| LightSleeper's profileLightSleeperBlogLists | Help |
|
LightSleeperGood times! June 26 API PrinciplesRecently I've been thinking about APIs. Pause for a moment to consider the best API family you've ever used. Dreamy, wasn't it? Everything worked the way you expected, you barely needed to look at the API reference, and the resulting code virtually documented itself.
Now think about the worst. I recall when I first started doing Windows programming and came across LPARAMs and WPARAMs in Windows messages. The whole concept of passing nebulous parameters that might be pointers or might be values was quite disconcerting. Or how about this fun little tidbit in the documentation for RegQueryValueEx: "If the data has the REG_SZ type, the string may not have been stored with the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS, the application should ensure that the string is properly terminated before using it." I wonder how many applications have been bitten by this API issue?
Creating great APIs is hard. But wow, is it important for those of us who ship code libraries to developers. When we don't get APIs right, there's nothing that can truly compensate for the failure -- not great documentation, not great sample code, not great user education.
Here are some principles my teams have been using as we think about APIs:
If you're developing your own API set, these guidelines can help ensure that your customers have that dreamy experience developers get when using a great API. You'll also spend less time having to update documentation, write complex sample code, or staffing a large support team.
June 11 The RevealThere have been many times in my game development career where I've worked on something shrouded in secrecy. In some cases it was games, in some cases game technology, and more recently, game consoles. I've been uncomfortably close to an IP leak around Xbox 360, when a confidential white paper with my name on it found its way onto the Internet
That's why it's so nice to finally be able to talk publicly about a project I've been working on that Microsoft recently announced at E3. Never mind that the technology is pretty sweet and that the Natal sensor has a good chance of revolutionizing the game industry. What's really great is that I no longer have to say "if I told you, I'd have to kill you" whenever somebody asks what I'm working on.
There are a slew of videos available showing Natal in action, but I'll leave you with one of my favorites:
May 15 The Magical Interdisciplinary ViewI've been reading Making Things Happen, by Scott Berkun. Great book.
One section was a revelation to me. It's called "the magical interdisciplinary view." In the business of making games, there are three perspectives: the business perspective (how does this game make money), the customer perspective (what motivates game players) and the technology perspective (how do we build this game and get it shipped). Scott's point is that all perspectives are important, and that they overlap. Business considerations have implications to the technical side and customer side, etc. He uses a Venn diagram to show that business, technology and customer each intersect.
As I look back, I can recall times when I've been too focused on one viewpoint to the exclusion of the others. The best products are a marriage of the right tech, biz and customer decisions, where each side is balanced and evaluated against the others. That's why the marketing team needs to understand technology, why the tech team needs to understand the game business, and why everybody needs to understand gaming customer needs and desires. If these teams aren't talking to each other regularly, you won't create the next great game.
April 30 Studios WestI joined Microsoft back in 2001. I've worked on the same Microsoft campus -- the Millennium campus in northeast Redmond -- until this week.
In the early days of Xbox development, Millennium campus was a great place to be. Xbox was a skunkworks project. We were off the radar. We were away from the main Microsoft campus, free to do our thing: ride skakeboards in the hallways, cover all the lights in green plastic, and race RC cars in the parking lot.
Then we shipped Xbox. Then Xbox LIVE. Then Xbox 360. We grew. A lot. The parking lot was too crowded. Sometimes it was impossible to find a spot. People were doubled up in offices, then tripled. We were spread across too many buildings, then spread all over Redmond. The magic of those early days was gone.
This week we moved to a new Microsoft campus called Studios West. It's a different style of building for Microsoft. The intent is a more collaborative workspace, and even after only a week, it's clear that the experience here will be much different. Better in many ways, though not all.
Everybody now has their own workspace again. Parking isn't a problem. The food options are much more diverse (though more expensive). The mall-like atmosphere in the Commons is a strange but welcome change. The opportunity to sit outside for lunch now exists.
The best thing is that we're all back together again. In those last few years at Millennium, the people that I used to see at least once a week in the early days were rarely glimpsed. They were spread far and wide. Now I see them again, and those impromptu connections and discussions are going to lead to better products and more delighted customers. There's some new magic in the air.
April 07 Tuning C++ Destructors, Part IILast time I posted a snipped of code that I examined as part of my GDC talk. This is an imperfect destructor in many ways. How many can you detect?
Enterprise::~Enterprise()
{
Unload();
m_ReactorCore.Empty();
if( m_pOrdnance != NULL )
{
delete m_pOrdnance;
m_pOrdnance = NULL;
}
if( m_hAlert != NULL )
{
CloseHandle(m_hAlert);
m_hAlert = NULL;
}
for( int i = 0; i < m_CrewSize; i++ )
delete m_Crew[i];
}
Potential Problem #1: Calling a function within a destructor. In the absolute worst case, Unload() is a virtual function, and the call invokes undefined behavior. By the time you enter a destructor, any subclasses have already been destroyed, so invoking a virtual function makes no sense. Even if Unload() is not virtual, it may be doing unnecessary work that can have a performance impact. If you’re writing a game and this destructor is being called hundreds of times per frame, Unload() may be overkill that’s worth avoiding.
Potential Problem #2: Calling a function within a destructor that could throw an exception. Destructors must never throw.
Potential Problem #3: Calling a member function to do clean up. Rule of thumb: any class should clean up after itself. That’s what destructors are for. The destructors for subobjects are called automatically after the destructor body is executed. Forcing users of a class to invoke a clean-up function is error-prone and unnecessary. It’s more likely that the .Empty() function is doing unnecessary work and can be eliminated completely.
Real Problem #4: Checking for NULL before calling delete. The C++ Standard guarantees that delete will correctly handle NULL pointers. Checking for NULL is redundant work that should be avoided.
Real Problem #5: Clearing out pointers in destructors. The Enterprise object is on the verge of utter destruction. Upon exit from this code, any access to the memory previously occupied by the Enterprise object invokes undefined behavior. True, you may be more likely to catch stray pointer issues by setting memory locations to known values, but clearing pointers (or any other memory) doesn’t buy you absolute or true protection. Furthermore, it can adversely affect performance in common destructors. Set memory to known values in debug builds only.
Potential Problem #6: Clearing out memory in destructors. The check for NULL prior to CloseHandle is valid and must remain. However, setting the handle to NULL buys you nothing but a false sense of security. See Real Problem #4. There is at least one case where clearing memory in destructors could be worthwhile – when you need to clear out an encryption key or other important game data from memory. My hackers will attempt to access or update critical values in memory, so clearing those values can be important.
[Note: some handles are properly compared against NULL and others are properly compared against INVALID_HANDLE_VALUE. Understand the API you’re using and what type of handle it expects and returns.]
Potential Problem #7: Freeing up pointers in a container. m_Crew contains a list of raw pointers to memory. The destructor is correctly cleaning up those pointers. However, there’s a better way: shared_ptr. If the m_Crew container owned a list of shared_ptrs, the shared_ptr destructors would automatically handle the cleanup. Furthermore, shared_ptr would allow for other lists and objects to maintain references to the pointers, guaranteeing no orphan pointers to garbage.
With the changes proposed above, here’s what the destructor would look like:
Enterprise::~Enterprise()
{
delete m_pOrdnance;
if( m_hAlert != NULL )
CloseHandle(m_hAlert);
}
That's good, but we can do even better. Let’s take the shared_ptr advice and apply it to the m_pOrdnance pointer. We can also create a little wrapper class around HANDLE objects that performs the same sort of work as shared_ptr:
class Handle
{
HANDLE m_Handle;
Handle() : m_Handle( NULL ) {}
explicit Handle( HANDLE h ) : m_Handle(h) {}
~Handle() { if (m_Handle != NULL ) CloseHandle( m_Handle ); }
}
Now we have perfection, the empty destructor:
Enterprise::~Enterprise()
{
}
All Enterprise subobjects are owned resources that automatically clean up after themselves. Not only is our code more readable and maintainable, it’s also more likely to be exception safe.
[Note: One commenter wrote that we should make the Enterprise destructor virtual, “just in case.” The Enterprise destructor should be virtual if and only if one could ever call “delete pEnterprise” when pEnterprise was actually pointing to a class derived from Enterprise. A good rule of thumb is to make the destructor virtual if there are other virtual functions in the Enterprise class.]
|
||||||||||||||||
|
|