Welcome back followers of the fearsome!
Since Jesse’s still a bit hung over from his birthday party last night, I (Nick) will be typing this weeks dev-blog. It’ll be programmer talk, but since Jesse can draw the turtle with his eyes closed now, he did manage to spruce up this post with an awesome turtle drawing.
I’ll be talking about loading screens. There are many games on iOS that have long loading screens, and every time I see them I wonder why. Some people I’ve met at the Full Indie meetup have told me that the iPhone is very slow to load data from. In my experience this couldn’t be further from the truth. It’s one of the fastest loading devices I’ve ever worked with! So, in order to possibly help out a few fellow developers remove those long-ass loading screens, I’ll give you some information on how I load data into our game. Note that this information is totally useless if you use Unity, but if you have your own engine (yea! rock on!), this info could make things a lot easier and faster!
There are different ways to load data from a disk. The most common method is to fopen a file on disk, and use fread to get the data from it. On consoles with DVD drives a common trick to speed up loading is to compress the data on the disk, and unpack it at load time in a separate thread. When loading compressed data, you often end up with a memory buffer to load the compressed data into, and another memory buffer to unpack the data to, and then possibly another memory buffer to store the data after you’ve loaded it. (The last step depends on if you load in place and fix up pointers, another common trick, but that’s for another time). In my experience, allocating extra memory, or using extra CPU to load the data is quite undesirable on mobile devices. I tried using our console engine approach of loading and unzipping data, but it was extremely slow. Then I read a post by Carmack (which I can’t seem to find in my bookmarks.. argh) about how he uses a function called mmap in Rage for iOS. You should learn everything you can about this function as soon as possible. Here’s a wiki page to get you started: http://en.wikipedia.org/wiki/Mmap
So what does mmap do? mmap can map an entire file into a virtual memory address. It doesn’t load any data, it just reserves a memory address that you can access, at which point it will load the data on demand. So all you do is access the data through a pointer and data will get loaded behind the scenes (without having to go outside the kernel, speeding up thing considerably!). The trick is that no memory is actually allocated for this, except for possibly a bit of cache memory. Doesn’t that sound pretty weird and magical? That’s because it is.
So how do we use this in Shellrazer? We pack all of our data files (thousands of them) into one large archive file at compile time. The archive file is nothing other than a list of file-names with their offsets in the archive, and the data for each file. All the data is uncompressed so that I don’t need to uncompress data when I want to access it. (By the way, if your game is a gigabyte in size, this may not be desirable for your users, but that’s another story) When I need to get some data from a file, I simply look in the list of files, get the offset inside the archive, and return a pointer to the mmapped archive plus the offset. Then I can just start using the pointer and read from it as if you’re reading from any memory location. If I have texture data I just pass this pointer straight to OpenGL, so it can do it’s thing to load the texture. It’s super easy to use, it works all the time, and it’s crazy fast.
In fact, when I first put this method in, I was shocked at how fast it was. According to a few tests I did I was streaming in data at a rate of 300Mb per second. The data in our game loaded so fast that the loading screen I added was barely visible. In fact, I had to make the loading screen stay on screen artificially for at least 0.3 seconds, otherwise it looked like a bug.
After these tests I altered the way our game handles data. I keep a few things in memory at all times, such as the main font bitmap, and a few other commonly used bitmaps. Everything else gets swapped in and out of memory, depending on the currently active UI-screen. When the game switches to a new UI screen, the old screen first transitions to our little loading indicator for about 0.2 seconds, then all the data gets flushed from memory, all the required data for the new screen gets loaded into memory, and the new screen transitions in for about 0.2 seconds. If you take a look at Shellrazer, every time you see a black screen with a little white rotating icon in the bottom right, that’s our loading screen, and in almost all of those cases, over 30Mb of data is loaded between two screens, plus it has about 0.4 seconds of artificial delay. Also, when our game goes into the background, I flush out all of the resources we use, and we reload these when the app goes to the foreground again, all of which takes less than half a second. Pretty speedy!
Now, as for application size, Apple has the 50Mb restriction on downloadable over 3G vs Wifi. Our archive file is 60Mb though. Well, the good news is that all data files get compressed by the Apple installer, so our download size for Shellrazer is only 31Mb. Problem solved. :)
Alright, I think that’s all I have to say about how to speed up your loading times. I probably missed a bunch of things, so if you have any questions about this stuff, I’ll be happy to help out!
tl;dr: use mmap to load, it’s awesome.