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