Search |
|||||||||||||
Timing is Everything
Tue, 2005-02-15
|
|||||||||||||
| ||||||||
Tick, Tick, Tick, Tick
Any time you introduce dynamic effects, animations, or time-based events to a Java application, you find yourself re-implementing the same functionality you have written for every application that required timing or animation. The built-in APIs are powerful, but they require that you write a fair amount of boilerplate code. This article considers the current situation and what is needed in a timing framework. The article refers to example code contained in a project posted on java.net: timingframework.dev.java.net.
There are so many different topics to discuss in this area, but for the purposes of this article, I will follow the introductory material with a look at these issues:
TimingController uses. TimingController: This section covers the major
interesting features in the TimingController utility.
I recently wrote some simple demo applications to show off a particular point for a presentation on dynamic effects in Swing. In the course of this, I once again realized what a complete hassle it is dealing with timing and animation issues.
For example, suppose I wanted to have a simple animation where I move an image across the screen back and forth ten times, each time taking 500 milliseconds. I'd like to update its position every 30 milliseconds (about 30 frames per second), I'd like to pause for 50ms before starting, and finally, I'd like it to hold its final position when it is finished. I should be able to write code akin to the following:
repeatCount = 10; // do the animation 10 times
duration = 500; // each animation lasts 500 ms
resolution = 30; // update very 30 ms
begin = 50; // wait for 50 ms before starting
repeatBehavior = Reverse; // reverse at the end of each animation
endBehavior = Hold; // hold final position
Animation anim = new Animation(repeatCount, duration, resolution,
begin, repeatBehavior, endBehavior);
Unfortunately, here is the code I would have to write instead (using
javax.swing.Timer):
class MyActionListener extends ActionListener {
public void actionPerformed(ActionEvent e) {
// Here's where all the logic goes: check the event time and
// see whether to reverse or end the animation based on the
// time elapsed
}
}
resolution = 30; // update every 30 ms
begin = 50; // wait for 50 ms before starting
ActionListener myActionListener = new MyActionListener();
Timer timer = new Timer(resolution, myActionListener);
timer.setInitialDelay(begin);
This snippet doesn't look too bad until you realize that
the real work is not yet done; implementing the insides of
myActionListener can be pretty tedious.
To be fair, the timing utilities in Java, java.util.Timer
and javax.swing.Timer, are pretty powerful and can help
you schedule time-based events. These classes let you create one-time
and repeating events that will call into your application at scheduled
intervals so that you can cause things to happen at those times.
This is a standard way to handle time-based events, such as animations; you are essentially telling the system "wake me up every n milliseconds." When you are woken up, you perform the appropriate action (change the appearance of an animating cursor, move an object on the screen, poll the network, send mail to your boss telling her how productive you are, whatever).
These are great utilities as far as they go, but I find that they don't go far enough. There is always other time-based functionality that I end up layering on top in order to deal with real application needs. For example, I usually want to run a timing event (such as the fading in of a component) for a set amount of time, but theTimer utilities assume
one-time or indefinite operation and must be manually stopped when you
are finished with them.
I thought it would be useful to codify some of the generic
functionality that I tend to need into a higher-level utility that
makes using timers a bit easier (at least for me). I've done this with
the TimingController and associated classes in the java.net project
TimingFramework.
Ideally, this framework could be
built upon to have more functionality that applications may need;
perhaps that topic is ripe for another article or project some day in
the future. But in the meantime, I hope that TimingController provides
the following things for developers:
Timer classes do everything you need, but that it's not clear
how to use them for your application. Hopefully, the sample code
will help demonstrate how to use Timers in general. TimingController hopefully provides enough
functionality that you can use it as is, or you can add to it for
greater or more application-specific functionality. TimingController, but I'm sure there are more common cases out there
that we could add to the framework as appropriate. Also, I've taken
some quick-and-dirty approaches to solving some of the problems so that
I could get the code and this article out there in short order. Perhaps
some feedback from the real world will help to improve the framework in
general and provide a more robust and general solution that developers
can use. TimingController as the first
step toward that eventual goal of World Animation Domination; it rounds
out the sharp edges of Timers and adds some important yet simple
functionality. I would like to add more functionality to this framework to
accomplish even great things eventually. But first things first. TimingController as
the first step toward that goal of SMIL-based timing technology.
So hopefully in the
future, when everything is totally integrated and available, you'll be ready to dive in.The timing model can be pretty confusing, especially when you try to incorporate ideas of simple timing loops inside of larger frameworks, such as the animation example above, which runs for 500ms but is repeated ten times inside a larger animation structure. In an attempt to simplify this model, I use terms in this article and in the code that describe these concepts in a logical way: cycle and envelope.
"Cycle" is the term I use to describe the lowest level of timing; it is a timing event that runs for "duration" amount of time with "resolution" amounts of time in between each timing event, as depicted in this diagram:

The diagram shows time progressing from left to right, along the arrow, with the beginning of the cycle at the far-left vertical line and the end of the cycle at the far right vertical line. Each tick shows a single timing event, which occurs with a frequency defined by the resolution.
The idea of duration is useful. Typically, in the demos I
write, I want the animation to run for a specific amount of time and
then stop. For example, if I want a component to fade in
over the period of a second, I will want an animation to run
that performs incremental fade-in capability (by changing the
AlphaComposite used by the component's paint() method) and then I want
that animation to stop one second after it starts. This framework
allows you to specify that duration and the timer will stop
automatically.
This definition of a cycle is fine for very low-level simple timing; we could build a utility that allows us to specify the duration and resolution, and we could have a timer that performs appropriately. This is basically just a simple layering of the duration concept over the current Java timers.
But we could also expand upon this idea by having the concept of a timing envelope, which I define to be a series of cycles. A simple diagram of an envelope follows:

In this diagram, we once again see time marching forward from left to right, but now we are seeing how cycles and their envelope interact. Each envelope has the following:
begin: The time when we will actually start the first Cycle.cycle(s): Some number of cycles, as defined above, which have
duration and resolution associated with them.repeatCount: The number of cycles to run. This number
determines the endpoint of the envelope in time; after the last cycle
is finished, this envelope is also finished.Some important things about envelopes that are not called out in the diagram (because I found them difficult to draw; maybe if I had another dimension or two at my disposal in my drawing tool I could manage it) include:
RepeatBehavior: Envelopes can choose to either Repeat or
Reverse at the end of each cycle. This will cause the next
cycle to either work forwards each time (Repeat) or to reverse
direction each time (Reverse). This will become clearer in playing
with the code and the SimpleAnimation example, but think about the
animation example at the beginning of the article. We could
choose to animate the image by moving it back and forth across the
screen (Reverse) or by moving it always left-to-right, popping it back
to the leftmost position whenever it finishes each left-to-right cycle
(Repeat).EndBehavior: Timing events can either Hold the final value at
the end of the envelope, or they can Reset the value to the initial
value when the envelope began.TimingControllerTimingController depends on the underlying Timer
capabilities of the
existing libraries, but layers on other pieces of functionality that I
have found useful in various applications. I'll highlight the main
points of the framework here, but looking at the code itself is
obviously the best way to understand how it all works. In particular,
the SimpleAnimation example uses all of the different features of
TimingController, so you can play with the features in the GUI and see
how they were implemented in the code.
Overview
TimingController uses the concepts of Cycle and Envelope, as
described above. Each TimingController is created with
a Cycle and an Envelope, like this:
TimingController myTimer = new MyTimerSubclass(cycle, envelope);
The Cycle object is created with:
duration: An integer value representing
the number of milliseconds that
each cycle should take. This value can also be
TimingController.INFINITE to represent that the
cycle will be unending.
resolution: An integer value representing the number of
milliseconds between each timing update (calls to
timingEvent(), below). A developer can determine
how often they would like to have their events processed (for
example, how many times they want to update an animation), and can set
this value appropriately.
The Envelope object is created with:
repeatCount: A double value that represents the number of cycles that
should be run before this Envelope is finished. This can be a
fractional value, which would cause the final cycle to end somewhere in
the middle. This value can also be TimingController.INFINITE, which
means that the Envelope is unending and that the cycle in the envelope
will continue repeating until the TimingController is stopped.
begin: An integer value representing the number of milliseconds
that should elapse before the first cycle begins.
RepeatBehavior: FORWARD or REVERSE, which
controls the direction of flow of each successive cycle. A
FORWARD behavior will start each cycle at the beginning
and continue to the end. A REVERSE behavior will start
each successive cycle at the place where the previous cycle ended.
EndBehavior: HOLD or RESET, which controls
the final value taken at the end of the Envelope. A HOLD
value will cause the Envelope to end sustaining the final value
calculated, whereas a RESET value will cause the
Envelope to end by resetting the final value to the initial value.
The TimingController object, after its start(),
repeatedly calls into the TimingController.timingEvent()
method with three values:
cycleElapsedTime: The time that has elapsed in
the current cycle.
totalElapsedTime: The total time that has elapsed
since starting the first cycle.
fraction: A floating point fraction that represents the
portion of the cycle that has elapsed.
Note that, at this point, TimingController puts the real work of
calculating values based on the timing events onto the application; the
framework does not interpolate values for you. However, given the
fraction of time that has elapsed in a cycle, it is usually
straightforward for an application to calculate values based on each
time event.
Note, also, that I use Cycle and Envelope in the code here to make it
easy to translate between the text in this article and the code in the
framework. In a real implementation of this framework, I might dispense with these
extra classes entirely. They do a nice job of documenting the timing
model structure, but
in reality, they add a level of indirection that real users might not want
to deal with constantly. But this more academic approach will do for now.
Timer Independence
There are various ways to use timers in your application, from
the existing Timer classes to running a thread of your own and doing
all of your own timer scheduling manually. I did not want to have any
visible dependencies on a particular scheme to limit what someone could
do with the framework. For example, the existing Timer classes have
certain implications for timing resolution; if you ask for a timer to
have a resolution of three milliseconds, but that timer uses a
low-resolution timer, then you may not get anywhere near that
resolution (see the section below on timing resolution for more
information on this). So instead of having a visible and obvious
dependency on one of these classes, this framework is independent of
the existing utilities. It may depend on these utilities internally,
but the behavior you get will not be constrained by these classes. In
the timing resolution example, we could write the framework to have a
fallback mechanism that works around the resolution constraints of any
particular Timer mechanisms.
The framework has a mechanism for firing time events that is
independent of existing Timer mechanisms. For example, the Swing Timer
utility calls an ActionListener with a timing event, which is not the
case with TimingController. When a timer event occurs in
TimingController, it will call its own timingEvent() method; your
application would subclass TimingController in order to receive this
event.
Animation Fraction
A very simple way to write animations is to simply increment or
decrement values by some amount every time a timing event occurs. For example, you might move an icon back and forth on the screen during
some animation. Every time the Timer event occurs, you increment or
decrement the x value of the icon position by one. This is fine for
simple demos, but the approach breaks down quickly for anything
complicated or where you need some dependable behavior.
For example, say you developed the application on some powerful
system with gobs of memory and a fast CPU. You set up a Timer to call
your application every five milliseconds, and your icon scoots across the
screen at a rate of about 200 pixels per second. Then you run that demo
on a system with little RAM, a slow CPU, and a very low-resolution
timer. Now your application may only get called every 50 milliseconds,
or may have very inconsistent performance and get called frequently
sometimes and infrequently at other times. Now your icon staggers
across the screen at a painful clip of 20 pixels per second, sometimes
running much slower than that due to system hiccups.
Video games were written this way many years ago; they looked great on their target systems, but when they were run on systems much slower or faster than the target systems, the games were unplayable. I once saw an awesome graphics demo that spun around a wireframe piano model as it played a song; the graphics of the piano keys were perfectly synchronized with the sounds of the keys striking the strings. On the target system, it looked and sounded great; a very nice piece of work. But then I ran it again a few years later, when graphics performance was much greater and it looked ridiculous; the graphics finished rendering in about a tenth of the time as before, while the music still played at the same speed. This demo was written with the speed of the original system in mind. The movement of the piano model was not based on realtime values, but rather on the speed at which the runtime graphics system could draw the wireframe model.
The best way to handle animations so that they perform similarly
across a wide variety of platforms is to base the animations on elapsed
time instead of just the frequency of timing events. In the animating
icon example above, this means you would base the position of the icon
not on how many times your event was triggered, but on the total time
elapsed for any given timing event. For example, if you wanted your
icon to move at a constant rate of 200 pixels/second, then you would
calculate the number of seconds elapsed in the animation from left to
right and multiply that number by 200 to get the proper x value.
The existing Timer utilities send out events that are used simply
to signal the recipient that a timing event has occurred. It is up to
the client to choose what to do with that event. A typical thing to do in an
animation would be to figure out how much time has elapsed in either
the entire animation or since the last time an event fired and then to
react accordingly.
I thought it would be more useful to send out the information the
application actually needs here, instead of just a vanilla "timer
fired" event (which is the way the current Timer classes work).
Specifically, I wanted to be notified with the fraction of a
given cycle that has elapsed. So if we are halfway through the
duration of an animation, I would like to receive a notification that
means "you are halfway through." Then I can easily use that fraction to
calculate the correct value of my time-based animation.
TimingController helps out here by sending an event to timingEvent()
with the fraction of completion of the current cycle.
We can then use this fraction to calculate
the appropriate value that we are trying to vary.
The fraction is useful in both the Repeat and Reverse behavior
modes. In the simplest example, a fraction of .25 tells us that the
animation is one-quarter of the way through. But suppose we are using a
Reverse cycle and we are now running backwards; a
value of .25 tells us that we are one-quarter of the way through the
cycle, even if we got to that value by animating down from the end to
the beginning of the cycle. That is, the fraction always
represents the fraction between the beginning and end of a cycle, no
matter whether we are running the timing sequence the first time, or in
a Repeat cycle, or in a Reverse cycle.
There should be little need for a full code walkthrough here; just check
out the code,
read the many comments, and play with the
SimpleAnimation sample program to understand how it all works. But I
will give some simple pointers to start you off.
The actual framework is in three classes in the timing package:
Cycle: This is a simple structure that holds the
resolution and
duration parameters of each cycle.Envelope: This is also a simple structure that
holds the
repeatCount, begin, cycleReverse, and endBehavior parameters of the
overall envelope.TimingController: This is where all of the
functionality in the
framework lies. The heart of the class is in the constructor (which
sets up the parameters used in the animation, Cycle and Envelope), the
start()
method (which actually starts the animation), and the internal
ActionListener
class (which is used to field the javax.swing.Timer
events and translate them into timingEvent()
events). Your application would subclass TimingController and
override timingEvent() in order to receive the timing events. The other part of the sample code is SimpleAnimation, which
is a simple GUI-based animation application used to test TimingController.
The application allows you to specify the parameters for the Cycle and
Envelope used in the application, and then start the animation
running. Here's what you see when you start it:

Most of the code in SimpleAnimation is used to set up
and run the GUI. The core functionality that actually sets up
and runs the animation is in the following lines in
ControlPanel.actionPerformed():
Cycle cycle = new Cycle(duration, resolution);
Envelope envelope = new Envelope(repeatCount, begin,
repeatBehavior, behavior);
animation = new Animation(animationView, cycle, envelope);
animation.start();
If you look at this code and then the first line of code at the beginning of this article, and you squint a bit, you can see that the approaches are pretty similar. Mere coincidence? I think not.
My hope is that TimingController can become a building block in a
larger framework of general animation utilities. There are many
places to go from here, but there are a few key directions that seem
worth calling out here:
TimingController is set in stone. In
fact, I just finished rewriting it completely over the last few
weeks. I think it accurately represents my current thoughts on the
basic functionality that I wanted to describe and implement, but there are
many ways of going about this, and some may be significantly better
than what I have done so far. So with feedback and more thought
on the matter, the code may evolve to suit.TimingController provides the events and the information
necessary to calculate these values, but it does not interpolate values
directly. This seems like a natural capability to add into the framework. TimingController with
respect to global timelines or timing interaction is pretty basic. Your
application controls when an envelope may start, but it has no easy way
of synchronizing multiple envelopes or events. A more involved
framework should include the idea of timelines and envelope
synchronization so that we can more easily hook up multiple envelopes
to synchronize their behavior.TimingController, in which
case we should just use those libraries directly.I hope that we can eventually accomplish much of what I've outlined
above. But it all starts with the basics, thus the TimingController
utility and sample code.
I could write a whole article (or more) on this topic alone, but I'll try to limit myself to the highlights here.
The issue here is that all platforms have different constraints on
the resolution of their default timing systems. Resolution here
means the amount of time between different time-based events. For
example, the amount of time that a call to Thread.sleep()
takes is dependent upon the resolution of some underlying timing
system, as is a call to System.currentTimeMillis().
For most applications, this topic is irrelevant. If you are a static GUI application, then I don't even know why you have read this far; you don't have any need of time-based events. If you do have some simple animations or time-based events in your application, then chances are good that the resolution you require is of far coarser granularity than that provided by the default timers on most platforms, so you don't need to worry about timing resolution in general.
This section is not about those applications.
This section is for developers of performance-sensitive applications, including those with fast animations or where framerate is very important (games come to mind). Control freaks, that's what they are; developers that demand fine control over the scheduling of their animations and events.
These applications may care that when they ask to sleep for 10ms,
they actually don't wake up for 16 milliseconds. Or when they ask a
Timer to call them 500 times per second, they actually only get
called 60 times per second.
The main problem is that the default timing systems on the native
platforms are, in general, of low resolution, by which I mean that
they do not have the granularity to deal with requests of two millisecond
wakeups. For example, in native code on Windows, the usual
way to find out how many
milliseconds have elapsed is the Win32 function GetTickCount(). This is
the native underlying utility used by System.currentTimeMillis()
on that platform. GetTickCount(), however, has a default resolution of
somewhere between 10 and 16 milliseconds, depending on the platform (for
example, my Windows XP laptop has a default resolution of 16ms). If
you call System.currentTimeMillis() on a platform with a
resolution of 16ms, then you will only get answers in 16ms
increments. So if you call it at one time, then call it one millisecond later, you
will either get an answer that is the same as what you had before or a
value that is 16ms greater (depending on whether you just crossed that 16ms boundary).
Similarly, Thread.sleep(long ms) is implemented using
the native platform's sleep() function, which again might
use a low-resolution timer by default. This may mean that when you ask
to sleep for, say, 10ms, you will actually sleep for 16ms instead.
There is much more to write on this subject (especially as to workarounds, bugs filed, fixes, etc.), but the main reasons I wanted to call it out in this article include:
System.nanoTime(): One of the new features in JDK
1.5 is the new System.nanoTime() method. This uses, in
general, a higher-resolution timing facility on the native platform. It
returns a different number than System.currentTimeMillis()
(for one thing, it's 1,000,000 larger since it's in nanoseconds; for
another thing, it's a time value that is useful only when compared to
other return values from that function). But if the constraints of this
number meet your needs (as they did for my needs in TimingController),
then it's a great way to get higher resolution time values. TimingController limitations: Currently, TimingController is
layered on top of the existing javax.swing.Timer utility.
I purposely hid that implementation detail inside of the class so that
TimingController is not stuck with that approach. But the implication
of using that Timer utility for now is that
TimingController suffers from the same low-resolution constraints as
that utility. For example, if you ask TimingController for a resolution
that is less than that which javax.swing.Timer provides,
you may be disappointed in the results. This can/should change in
future iterations of TimingController, but it is an important thing to
note in the current incarnation. Note that the advice from the "Timing Fraction" section above applies well here; even if you are stuck with a timer with a lower resolution than you think you need, calculating your values based on elapsed times should give you what you need. Maybe you wanted your sprite to zip across the screen at 5000 times per second and you're only getting callbacks 60 times per second. It's unfortunate that it didn't match your ideal, but your icon will still look pretty smoothly animated at that lower rate, and it should look to the user as if it is moving at the same speed. It is just that the sprite's position will not be updated as often as you wanted.
Conclusions
I hope, foremost, that this article helps you to understand how timing works in Java in general. I also hope that you will download the code, play with it, understand it, and send in comments to help improve the framework. It would be wonderful to build on what we have and to create a more robust, functional, and general framework for assisting in all kinds of time-based applications.
Reminder: check out the code for this article at: timingframework.dev.java.net.
|
|