It's been a while, and I've learned a lot!
Saturday, March 17, 2012 at 3:24PM Dear me. Shortly after the last post, I was contacted by a recruiter (it had nothing to do with this blog, I think -- I had also posted my resume to dice) and swiftly hired by SCEA as QA Graphics Engineer. It's been one hell of a ride.
My toy engine was put on hold as I geared up for work in the real world, and I've learned a lot since then. A lot of is common sense, like "going to work hung over is infinitely worse than working from home hungover" while the rest of it is just more of how the world works, and how some people in the industry work.
Imagine how I, basically a newly annointed knight of C++11, reacted when faced with a code base that was written in what appeared to be a "C with classes" style than even a C++03 style. While not unexpected given what I had heard about game programmers, it was nonetheless uncomfortable.
But after working with that style for a while I realized why they like it so much: it's damn simple to understand. Everything is transparent. If you want to see how something works, you just peek at it and with a few pokes, you're done.
It took a while longer to realize that this wasn't because it was "C with classes", it was just that the original programmers wanted things to be simple! In the toy engine I was working on in July, things were just complicated. I thought that this was because, well, engines are complicated, but really it was because I had made it way more complicated than it needed to be. I was trying to serve edge cases that most likely would never be encountered, and even if they were, probably would be better suited to specially adapted code than extraordinarily complex general code.
At first I wasn't allowed to use C++11 features anyway, since we weren't sure about how they would be supported. It's a good thing too -- it took even longer for me to realize that my understanding of these new features had yet to really crystalize, and I was overagressively using them. It's like the old adage "if all you have is a hammer, everything looks like a nail." I was so blinded by how much my new hammer shined that I forgot how good my old tools were, and why they were there in the first place.
Let's take smart pointers as an example. I espoused strict usage of smart pointers over raw pointers, without actually thinking about what they were for. Thankfully, smarter and more experienced programmers have given several talks on smart pointers since then, and have pounded into my thick skull that once again, not everything is a nail.
The thing is, you only need a smart pointer if you care about the pointer's lifetime, and generally, you only care about the lifetime of an object if you're going to store a pointer to it. If all some function does with its Bar pointer parameter some simple operation without copying the pointer, then there's no reason to even pass a smart pointer. In fact, there's no reason to pass a pointer at all! Just use a Bar& and pass the onus of nullptr checking onto the caller (unless of course nullptr is a valid input--then use a raw pointer). There's no reason to incur any of the cost of a smart pointer in such a case.
There are also cases where even if you're storing the pointer, there's no reason to use a smart pointer. An example often used by Herb Sutter when talking about smart pointers is when you have a parent->child relationship, and the child needs a pointer to its parent. In my old engine, I was trying to do things like use weak_ptrs and thus I had to ensure that the parent was actually kept somewhere in a smart pointer and oh crap it was such a mess. In my zeal I had ignored the obvious conclusion that a deleted parent should also delete it's children, and thus a smart pointer was the wrong tool for the job.
Now that I have far more knowledge of the code base under my purview at work, I've started slowly adding in C++11. Mostly just auto, and some unique_ptrs and here and there. I'm slowly phasing out the "C with classes" style into a more C++ style, but I'm still trying to keep things as straightforward as they were. This is proving difficult however, as number of cases that the code needs to handle now has grown over an order of magnitude.
I've also started my engine again from scratch. It took me a long time to get settled in enough that I didn't feel totally lethargic after returning home from work, but I'm kind of glad it did. At first, I wanted to just apply what I had learned from work, but now I'm also trying more exotic things as well, to see where they fit and what they're useful for. Fun things like SFINAE, but it's going to be a long long time before I ever suggest SFINAE for anything in production code. I've written a bunch of SFINAE templates only to rip them back out because, in the end, the same end result could be accomplished in a more straightforward and easily understood method.
Learning how D3D works has helped a lot too. I don't like COM pointers much, but the API itself is an interesting contrast to OpenGL. Seeing some of the Sony console APIs helps a lot too in understanding what exactly OpenGL/D3D abstract away from you in order to work for different systems. For my toy engine, I've decided to go with a more D3D style adapter layer, except using actual smart pointers instead of COM pointers (of course, the D3D implementation has custom smart pointers to wrap the COM pointers, which seems to be working so far). I think it's way easier to implement this kind of adapter layer for OpenGL than it is to adapt an OpenGL style layer to D3D.
That's probably enough for now. I'll write about some of the sillier stuff (that works surprisingly well) I'm doing in the engine later.