Work Log - Bricking iPhones

Jul 11, 2009 06:10

Earlier this year, my startup was getting ready to unveil itself to the public. I felt that before we showed the game engine and tools to potential investors, we needed to do some leak checking and performance tuning. The head of the company disagreed until I showed that our engine bricked the iPhone in 15 minutes.



For leak checking, or really any debugging, I follow a binary search methodology - eliminate as much information as possible in the early stages in order to focus the majority of the time on the actual issue.

Apple's memory leak checking tools work pretty well, if you're not doing anything like using a scripting language for most of your game logic. This basically meant that a majority of the leaks were coming from a single call that invoked Lua functions. Argh. The first thing to do is cast a wide net, just to make sure you're not making any erroneous assumptions. I commented out everything in the main loop (which is in Obj-C) except for the iteration code itself just to make sure we weren't leaking memory from the application layer. Nope - airtight. Initialization and teardown were good.

There's usually a single entry point into the Lua side of code. Within this function, I commented out everything so that any calls into Lua would return right away. I figured this would eliminate all memory leaks since the only thing we'd be doing during the main loop is calling into an empty function in Lua and returning. Oddly enough, this resulted in memory leaks.

Going into the Objective C function that called into Lua, I found that hollowing out that function cleared up the memory leaks. The source of the leaks came from the actual Objective C function that was invoking the Lua functions. When we pass data back and forth between Lua and native code, we need to push the name of the function and its arguments onto the stack. We also need to clean up the stack after the return from Lua, and this was causing some of the memory leaks.

Stack cleanup code did the trick. We could invoke our empty Lua function and not leak memory. There are some common control paths that our game engine would execute. The next step was to go through a minimum set of functionality to make sure nothing there leaked. In general, I find a good test case to be half the battle. If you can create a test case that eliminates all noise from your test, you're half-way there.

My first test case was an empty game - no actors. Also, no memory leaks. Awesome. Next test case, one actor, no images. No memory leaks. Great! What if the test game deletes the actor? Leaks. I followed the same strategy here for actors - comment out all code for initialization and teardown (actors don't get deleted until the level changes or the user quits the game). The actors were initializing a native object - their collision shape, but weren't deallocating their collision shape on teardown.

This led to a breakthrough - if we weren't deallocating objects initialized from native code in the actor teardown code, we might be having this same error elsewhere. A search through the Lua code base showed that textures, and audio streams were likewise hanging around in memory after the Lua objects referencing them had been garbage collected. We needed to explicitly deallocate these native code objects.

After a frantic two weeks of work, we could confidently show our games to potential investors without worrying about hard rebooting our iPhones.

work

Previous post Next post
Up