devstruggle

Baby Steps: Project 0

In the time since my last post, I actually passed the Oracle Java 8 OCA exam. My last little challenge to myself from the Put Up or Shut Up post had a rather profound effect on my motivation to deepen my knowledge of programming. The truth is, the main reason I probably issued myself such a challenge in the first place was as an an attempt to avoid the struggle of studying for the OCA. The exam is challenging in itself, add to that the very short mental deadlines I tend to set for myself, and you get a recipe for procrastination. After starting work on the project, it occurred to me that it might be smarter to expend that energy doing something that led to a tangible benefit, ie. an industry certification, rather than just a fleeting sense of accomplishment.

With all that said, I did see significant value in building a project like the one I described. Just the planning phase forced me to build a detailed mental model of the program, which ended up being much more complex than I had initially imangined. This forced me to begin deconstructing the model into simpler components which would be easier to construct. These components will need to interact with each other, which ultimately forced me into a systems mindset. This post will begin at the deconstruction stage. Project 0 presents a very rough implementation of what to me is the core component of my little game engine, the game loop.

I've always been fascinated by animation. The simplicity of it is actually somewhat alarming. That a movie or video game consists only of a series of still images displayed in rapid succesion is somewhat difficult to believe until you look at a film strip or dig into the code of a video game and see that the final result is literally rendered a single frame at a time. Game loops have always been somewhat beyond me. As I wrote in the previous post, I've researched game loops in quite a few books and seen some of the most common approaches to implementing one. What I've always sought though, is the essence of a game loop, the most fundamental aspects of it, the most minimal, explicit game loop possible. When I say explicit, I mean one procedurally programmed with each major step explcitly stated, and not using some language feature providing something as succinct as say:

System.animation.loop(this.render(), frameRate)

which I'm sure exists in some form in a language or library I don't know. What I've discovered, to as much delight as dismay, is that game loops derive from concerns fairly deep under the surface of computer operation.

In the early days of the PC, animations would run as fast as the computer would allow. If a program drew an image of a car, and then moved it across the screen, the speed at which the car moved was defined solely by how fast a particular computer's processor could execute the program. As expected, on a slower computer, the car would move slowly, faster on a faster computer. The problem with this may be easily displayed by attempting to run an old BASIC program that generates an animation on a modern multicore computer. It is likely that the animation will move so fast it may not even be seen on screen. As computer games are nothing more than animations that accept input, it is easily understood how a game that runs this fast would be completely unplayable on a sufficiently powerful PC. Game developers in these early times got around this problem by inserting code that would periodically do something meaningless like count to one million between frames just to slow faster computers down. A better solution to this problem came later in the form of a way to check the system time. It may not seem like much, but if I can tell just how much time has passed since the last time I did something, I am able to define a certain frequency at which to repeat it. In other words, if I want to draw something on the screen only 60 times every minute, I may simply decide to only draw it once every second even though my processor may be fast enough to do so 10,000 times per second. Since the length of a second doesn't change between computers, this allows conistent performance on all systems.

Project 0 attempts to capture this simple concept. What has consistently tormented me about game loops is the when and where of execution. The fundamental algorithm to derive the timing is fairly straight-forward. This is the method that provides the timing for Project 0:


...
16  protected void runCounter() {
17    startTime = System.currentTimeMillis();  
18    while(true) {
19      currentTime = System.currentTimeMillis();
20      elapsedTime += currentTime - startTime;
21   
22      if(elapsedTime >= 1000) {
23          Switch.flip();
24          elapsedTime = 0;
25      }
26      startTime = currentTime;
27    }
28  }
... 
              

When the method is called, the first step is to capture the moment execution began into startTime using Java's builtin method that returns the number of milliseconds since January 1, 1970 as a long value. The while loop, which here is just an infinite loop for simplicity, is initialized on line 18 and the first step inside the loop is to capture this instant into currentTime. Next we calculate the time difference between the initializations of startTime and currentTime and add it to elapsedTime which will keep track of how much time has passed since the method was called. Next we test if 1000 milliseconds (one second) have passed by checking whether the value in elapsedTime is greater than or equal to 1000. If it is, we call a method. Switch.flip() is basically the placeholder for any action that needs to be executed at a certain frequency. This could be rendering code, or state update code, or anything else we want to happen every second. As the last step in the conditional statement, we reset elapsedTime to zero so that the next iteration starts with a fresh counter. When our if statement ends we set startTime to currentTime. This is critical because this re-initializes startTime for the beginning of the next iteration. Resetting elapsedTime is obviously important as its clear that once it reaches 1000, the 'timer' functionality of the method would fail as the if code would always execute. What wasn't nearly as obvious to me inititally was that the same is true of startTime. This gets into the when and where issue I referred to, if startTime isn't reset at all once elapsedTime has reached 1000, even though it is reset inside the if, each time elapsedTime is calculated it will always be greater than 1000, again destroying the timer functionality. Now that we know it's necessary to reset startTime, the question becomes where? It works fine to reset it before or after the if block. I chose to place it after, just before the end of the iteration to place it as close as possible to the start of the next iteration.

To be continued... Initial Post: April 3, 2019 7:56PM CST