Fighting immechanacy on the web

Simulation Clocks (Conclusion)

Oct 27, 2001
Fred Klingener


An earlier page (Java 3D Simulation Clocks - October 3, 2001), outlined requirements for clocks to control simulations of physical processes on Java 3D, proposed some possible arrangements, offered test sample applets, showed typical results, and finally concluded that none of the proposed architectures offered performance that was regular enough or had consistent enough cross-platform characteristics to warrant further work.

This page describes another approach that relies on a timer thread similar to the one used in Plan F from the earlier study, but seems to give more regular performance. First, some background in the use of threads in animation.

Animation . . . in 21 Days

Nearly every introductory tutorial on Java animation suggests the following general loop arrangement

        {      t += dt;
                try (Thread.sleep(dt); }
                catch(InterruptedException e) {}

or something similar. In this simple animation loop, a single thread is responsible for computing the geometry and rendering it to the screen. The frame rate is slowed and CPU cycles are made available to other programs or processes by the insertion of the sleep() method.

Figure 1. . . . 21 Days Java Animation

The timing diagram on Figure 1 shows the relationships. The frame cycle time is the sum of the compute time, the rendering time, whatever overhead tasks the CPU must execute and finally the sleep delay. This arrangement flourishes because it successfully manages simple animations and informal simulations. However, it's clear that the frame cycle time depends on the complexity of the model as well as the speed of the hardware on which the program is run. Not a good thing if you have ambitions to do hi-fi simulations with screen clocks that have some controllable relationship to the meat clock.

To be sure, in some cases where the model is relatively stable, the average mismatch can be measured and the sleep time adjusted so that the frame cycle time approximates the simulation clock advance. But simulations don't have to get very complicated for this approach to break down. For example, inverse kinematics computations impose a much heavier compute load on the system when the model state passes near a singularity, and the proper response is usually to reduce the time-step size. With a static correction, the display would slow as the compute block grew.

Clearly, better control of the frame cycle time and its relationship to the advance of the simulation clock is required for hi-fi simulations.

Java 3D WakeupOnElapsedFrames(0)

Before we go any further, we ought to describe the 'standard' timing of a Java 3D animation. Java 3D appears to have a default frame timing approach, and that default is directed at producing the maximum possible frame rate. Most of the animated demo programs that ship with Java 3D are based on the API's interpolator methods, and those methods are based on Behaviors that are set to WakeupOnElapsedFrames(0). As a result, the program free-wheels between computation and rendering., producing maximum frame rate and a 100% CPU utilization from a time line that looks something like that shown in Figure 2.

Figure 2. Java 3D WakeupOnElapsedFrames(0)

Java 3D - WakeupOnElapsedTime(dt)

Java 3D Behaviors have available a wakeup criterion that offers to WakeupOnElapsedTime(dt). The criterion seems to promise a time line that looks something like the one shown on Figure 3.

Figure 3. Java 3D WakeupOnElapsedTime(dt)

Unfortunately, with the API at 1.2.1, the OS dependencies make this approach impractical. (See the earlier page for typical performance measurements). Some superficial trials on J3D v.1.3beta1 show that this basic limitation remains.

Halfway Decent Clock

Java 3D - Oneshot Thread in a Runnable Behavior

Where the default Java 3D rendering cycle ran at full speed, and the '. . . 21 Days' arrangement added a fixed delay between frame cycle steps, the arrangement described in this section attempts to control the overall frame cycle time with a 'oneshot' timer thread. It's called 'oneshot' after the control system timer that has the same behavior. At time t, a oneshot timer is started (or turned on, or turned 'true') and after some prearranged interval, say dt, it stops (or turns off, or turns 'false'). At a particular stage in a cycle, the controller triggers the oneshot, then at the same stage of the following cycle, the controller waits for the oneshot to time out before it continues. In this way, the minimum frame cycle time is held at dt. The frame cycle time can be greater than dt if the controller has tasks take longer than dt, but it can't be less.

Listing 1 shows a skeleton of one way to implement a oneshot using the Java Thread.join() method.

Listing 1. class SimulationClock

        public class SimulationClock extends Behavior implements Runnable
        {      // clock
                long t;         // simulation clock time (ms) say
                int dt;         // time step
        //      wakeup alarm
                wakeupCriterion set;
        //      thread
                Thread oneshot;
                . . .

To implement Runnable, our clock has to provide a run() method. In this case, the sole function of that method is to sleep for dt milliseconds.

        public void run()
        {      try
                catch(InterruptedException e) {}

As a Behavior, the class has to provide initialize() and processStimulus(. . .) methods:

        public void initialize()
        {      t = 0;
                dt = 45; // say
                set = new WakeupOnElapsedFrames(0);
                . . .
        public void processStimulus(Enumeration e)
        {      t += dt;
                . . .
        //      compute model geometry
                . . .
        //      hold up processing until oneshot times out
                if (OneShot != null)
                {      try {OneShot.join();}
                        catch(InterruptedException je) {}
        //      then construct a new one and start() it
                OneShot = new Thread(this);
                . . .

Figure 4 shows the schematic timeline of a oneshot-controlled animation cycle with a light compute load, in which the oneshot thread is still alive (or its control system counterpart would be 'on' or 'true') when the main thread (or the controller) is ready to begin the next cycle. The main thread (or controller) is held up until the oneshot exits its run() method and dies (times out.) The frame cycle time is equal to dt..


Figure 4. Oneshot Runnable Behavior. Fast processor

Figure 5 shows a schematic time line for a oneshot-controlled animation cycle with a large compute load, in which the oneshot dies out before the main thread has completed its other tasks. The resulting frame cycle time is greater than dt.

Figure 5. Oneshot Runnable Behavior. Slow Processor.

The chart in Figure 6 shows the schematic behavior of a hypothetical oneshot-controlled animation in which the total compute load takes 35 milliseconds (ms). The scheduled frame cycle time, dt, is plotted along the x axis, the realized cycle time (in red) against its axis on the left, and the percentage of frames that are held up at the join() by the oneshot (in blue) against its axis on the right.

For dt less than 35 ms, the frame cycle time is pegged at the irreducible compute time of 35 ms, and for dt greater than 35 milliseconds, the oneshot limits the cycle time to dt.

In this simple-minded model, no frames are delayed when the oneshot's dt setting is less than the compute time of 35 ms, and 100% of them are when its setting is greater than 35 ms.

Figure 6. Frame Delay and join() % vs. dt for the idealized model.


So, considering that the concept of the oneshot design was sort of fanciful - Java Threads don't work like deterministic programmable controllers - how well does it work on a real animation? Amazingly well on the average, according to the charts below.

On one of the simple rotating-cube applets patterned after the HelloJava3D demo, the oneshot clock shows agreeable (or at lest manageable) cross-platform performance. The charts on Figure 7 show the comparative performance on two Windows OSs.

Figure 7. Comparative performance of a Oneshot Frame

The applet used to generate the charts in Figure 7 can be run here.

VMDemoApplet is a modest but still serious inverse kinematics animation that has a fairly large compute load. The computer on which the sample applets were run is at the bottom end of acceptable performance for Java 3D (300MHz Pentium, 128Mb memory, FireGL graphics card, W2k OS)

The chart on Figure 8 has the same layout as the one in Figure 6, and the shapes of the curves are identifiably similar. One of the first things to notice is that the behavior of the Java threads is probabilistic. The transition from all-frames-timed-out to no-frames-timed-out spans about 15 ms. Next, at larger values of dt, the frame cycle time exceeds dt by a more or less constant offset of about 12 ms.

Figure 8. Frame Delay and join() % vs. dt for a real Java 3D model.

The chart is only typical. At various times, in response to mysterious and unknown system conditions, the curves might shift or change shape. Oddly regular spikes appear and vanish on successive runs. The offset shrinks and grows. On a freshly rebooted computer, the curves might be ragged or smooth.


The oneshot architecture for frame timing seems to offer significantly more regular and more predictable behavior for a Java 3D simulation clock than any of the other candidates.


May 15, 2002. Reportedly, Java 3D 1.3 has reshuffled the time-handling, and it's time to redo some of the old benchmarks. Here's the trace produced by VMDemoApplet (a somewhat slower version of it by the addition of the motor and gearbox.

Still pretty presentable, but the down spikes whenever dt is an even multiple of 10ms with 0% join delay and the upspikes above 100% join delay are baffling.

July 12, 2002

The VMDemoApplet incorporated a JEditorPane in a way that broke somewhere around the introduction of J2SE 1.4. Once I get that fixed, I'll post the URL for it and rerun the charts.