Renoki: Making An Android Game (part 13)
I discovered a very annoying thing recently that made me have to reconsider how I handled touch input.
Originally, my game consisted of a game loop that updates the state of all the sprites and then draws them onto the screen. Touch input was handled asynchronously, meaning I need a lock around my update section. The reason is that I don’t want touch input to affect the state of anything while I’m in the middle of updating, otherwise I could have a race condition where the update section is about to jump to state X, the user presses a button to go to state Y, and then the update section resumes and pulls it back to state X.
Now I understood the problems that could arise from this. Touch input needs to be processed relatively quickly or you get Android Not Responding errors. I felt that even if my touch input had to wait for 1 or 2 update loops to finish, that is still only fractions of a second. Unfortunately for me, sometimes the Android kernel will decide not to give the touch input priority to run. Instead, it will allow the update loop to keep acquiring the lock over and over, resulting in the dreaded ANR. And there wasn’t any sort of fair lock I could use, because I was just using Java’s “synchronized” command, which is supposed to handle locking for you.
I realized that I would have to make a queue of all touch events. When a user touches, it enqueues into a touchQueue. In each loop iteration, I dequeue 1 touch and process it. This way, you only have to lock around the touchQueue, which greatly reduces the chance of a touch event getting blocked.
It’s at this point that I realized something else stupid. When a touch occurs, it calls some onTouchEvent(MotionEvent event) and passes you a MotionEvent object which describes properties like the x, y coordinates, and whether it was a down or up press. Unfortunately, you need to make a copy of the MotionEvent it passes you, because after the onTouchEvent() is done, it will reuse that object. I sat around for a while, wondering why the MotionEvents in my queue were getting corrupted and it took me forever to realize this.
Now you need to consider having a pool of Events. It’s inefficient to recrete a new Event object every time a new touch is made. It’s better to have a pool of Event objects. When a new touch is made, you request an Event object from the pool. If the pool is empty, it creates a new one for you, otherwise it hands you a pre-existing one. When you are done with the Event object, you add it back to the pool for future touches to use. This saves on the overhead of dynamically allocating a new Event object every time there is touch input.