/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.loop;
import org.jagatoo.util.timing.JavaTimer;
import org.jagatoo.util.timing.TimerInterface;
/**
* This is the base for any threaded operation. RenderLoop uses it and you can
* use it in OperationScheduler, etc.
*
* @author Marvin Froehlich (aka Qudus)
*/
public abstract class UpdatingThread implements Runnable, Updatable, GameTimeHost
{
/**
* This enum contains constants to control the timing of this thread.
*
* @author Marvin Froehlich (aka Qudus)
*/
public static enum TimingMode
{
MILLISECONDS( 1000L, 1L, 1000L, 1000000L ),
MICROSECONDS( 1000000L, 1000L, 1L, 1000L ),
NANOSECONDS( 1000000000L, 1000000L, 1000L, 1L );
private final long divisor;
private final long milliDivisor;
private final long microDivisor;
private final long nanoDivisor;
private final float floatDivisor;
private final float floatMilliDivisor;
private final float floatMicroDivisor;
private final float floatNanoDivisor;
/**
* @return the divisor to divide by to get full seconds.
*/
public final long getDivisor()
{
return ( divisor );
}
/**
* @return the divisor to divide nanos by to get millis.
*/
public final long getMilliDivisor()
{
return ( milliDivisor );
}
/**
* @return the divisor to divide nanos by to get micros.
*/
public final long getMicroDivisor()
{
return ( microDivisor );
}
/**
* @return the divisor to divide nanos by to get <i>this</i>.
*/
public final long getNanoDivisor()
{
return ( nanoDivisor );
}
/**
* @return the divisor to divide by to get full seconds.
*/
public final float getFloatDivisor()
{
return ( floatDivisor );
}
/**
* @return the divisor to divide nanos by to get millis.
*/
public final float getFloatMilliDivisor()
{
return ( floatMilliDivisor );
}
/**
* @return the divisor to divide nanos by to get micros.
*/
public final float getFloatMicroDivisor()
{
return ( floatMicroDivisor );
}
/**
* @return the divisor to divide nanos by to get <i>this</i>.
*/
public final float getFloatNanoDivisor()
{
return ( floatNanoDivisor );
}
/**
* @param t the input time measured in millis, micros or nanos
*
* @return the corresponding millis
*/
public final long getMilliSeconds( long t )
{
switch ( this )
{
case MILLISECONDS:
return ( t );
case MICROSECONDS:
return ( t / 1000L );
case NANOSECONDS:
return ( t / 1000000L );
default:
throw new Error( "This conversion is not yet implemented." );
}
}
/**
* @param t the input time measured in millis, micros or nanos
*
* @return the corresponding micros
*/
public final long getMicroSeconds( long t )
{
switch ( this )
{
case MILLISECONDS:
return ( t * 1000L );
case MICROSECONDS:
return ( t );
case NANOSECONDS:
return ( t / 1000L );
default:
throw new Error( "This conversion is not yet implemented." );
}
}
/**
* @param t the input time measured in millis, micros or nanos
*
* @return the corresponding nanos
*/
public final long getNanoSeconds( long t )
{
switch ( this )
{
case MILLISECONDS:
return ( t * 1000000L );
case MICROSECONDS:
return ( t * 1000L );
case NANOSECONDS:
return ( t );
default:
throw new Error( "This conversion is not yet implemented." );
}
}
/**
* @param t the input time measured in millis, micros or nanos
*
* @return the corresponding seconds as float
*/
public final float getSecondsAsFloat( long t )
{
return ( (float)t / this.getFloatDivisor() );
}
/**
* @param t the input time measured in millis
*
* @return the corresponding time units measured in the current TimingMode
*/
public final long getFromMilliSeconds( long t )
{
switch ( this )
{
case MILLISECONDS:
return ( t );
case MICROSECONDS:
return ( t * 1000L );
case NANOSECONDS:
return ( t * 1000000L );
default:
throw new Error( "This conversion is not yet implemented." );
}
}
/**
* @param t the input time measured in micros
*
* @return the corresponding time units measured in the current TimingMode
*/
public final long getFromMicroSeconds( long t )
{
switch ( this )
{
case MILLISECONDS:
return ( t / 1000L );
case MICROSECONDS:
return ( t );
case NANOSECONDS:
return ( t * 1000L );
default:
throw new Error( "This conversion is not yet implemented." );
}
}
/**
* @param t the input time measured in nanos
*
* @return the corresponding time units measured in the current TimingMode
*/
public final long getFromNanoSeconds( long t )
{
switch ( this )
{
case MILLISECONDS:
return ( t / 1000000L );
case MICROSECONDS:
return ( t / 1000L );
case NANOSECONDS:
return ( t );
default:
throw new Error( "This conversion is not yet implemented." );
}
}
private TimingMode( long divisor, long milliDivisor, long microDivisor, long nanoDivisor )
{
this.divisor = divisor;
this.milliDivisor = milliDivisor;
this.microDivisor = microDivisor;
this.nanoDivisor = nanoDivisor;
this.floatDivisor = (float)divisor;
this.floatMilliDivisor = (float)milliDivisor;
this.floatMicroDivisor = (float)microDivisor;
this.floatNanoDivisor = (float)nanoDivisor;
}
}
public static final int PAUSE_NONE = 0;
public static final int PAUSE_TOTAL = 1;
private TimerInterface timer = new JavaTimer();
private TimingMode timingMode = TimingMode.MICROSECONDS;
private long t0 = -1L, t02, t03, now;
private long gameNanoTime = -1L;
private GameTimeHost gameTimeHost;
private long iterations = -1L;
private FPSLimiter fpsLimiter = new DefaultFPSLimiter();
private Thread thread = null;
private boolean isStopping = false;
private long minItTime;
private long bruttoFrameTime;
private int pauseMode;
/**
* Sets the timer used by the loop.
*
* @param timer could be an instance of org.xith3d.utility.timing.JavaTimer, e.g.
*/
public final void setTimer( TimerInterface timer )
{
if ( timer == null )
throw new IllegalArgumentException( "timer MUST NOT be null." );
this.timer = timer;
}
/**
* @return the timer used by the loop.
*/
public final TimerInterface getTimer()
{
return ( timer );
}
/**
* Sets the minimum time, an iteration must take.
*
* @param minItTime
*/
protected final void setMinIterationTime( long minItTime )
{
this.minItTime = minItTime;
}
/**
* @return the minimum time, an iteration must take.
*/
public final long getMinIterationTime()
{
return ( minItTime );
}
/**
* Sets the FPSLimiter capable of limiting the FPS/iteration time.
*
* @param fpsLimiter
*/
public void setFPSLimiter( FPSLimiter fpsLimiter )
{
this.fpsLimiter = fpsLimiter;
}
/**
* @return the FPSLimiter capable of limiting the FPS/iteration time.
*/
public final FPSLimiter getFPSLimiter()
{
return ( fpsLimiter );
}
/**
* @return the count of iterations done by this RenderLoop
*/
public final long getIterationsCount()
{
return ( iterations );
}
/**
* Sets the timing mode for the <b>frameTime</b>.
*
* @param mode
*/
public final void setTimingMode( TimingMode mode )
{
this.timingMode = mode;
}
/**
* @return the timing mode for the <b>frameTime</b>.
*/
public final TimingMode getTimingMode()
{
if ( gameTimeHost == null )
return ( timingMode );
return ( gameTimeHost.getTimingMode() );
}
/**
* {@inheritDoc}
*/
public final long getGameNanoTime()
{
if ( gameTimeHost == null )
return ( gameNanoTime );
return ( gameTimeHost.getGameNanoTime() );
}
/**
* {@inheritDoc}
*/
public final long getGameMicroTime()
{
return ( getTimingMode().getMicroSeconds( getGameTime() ) );
}
/**
* {@inheritDoc}
*/
public final long getGameMilliTime()
{
return ( getTimingMode().getMilliSeconds( getGameTime() ) );
}
/**
* {@inheritDoc}
*/
public final long getGameTime()
{
return ( getGameNanoTime() / timingMode.getNanoDivisor() );
}
/**
* {@inheritDoc}
*/
public final long getLastNanoFrameTime()
{
return ( bruttoFrameTime );
}
/**
* {@inheritDoc}
*/
public final long getLastFrameTime()
{
return ( getLastNanoFrameTime() / timingMode.getNanoDivisor() );
}
/**
* Sets the pauseMode.
*
* @see #PAUSE_NONE
* @see #PAUSE_TOTAL
*
* @param pauseMode
*/
public final void setPauseMode( int pauseMode )
{
this.pauseMode = pauseMode;
}
/**
* @return the pauseMode.
*
* @see #PAUSE_NONE
* @see #PAUSE_TOTAL
*/
public final int getPauseMode()
{
return ( pauseMode );
}
/**
* {@inheritDoc}
*/
public abstract void update( long gameTime, long frameTime, TimingMode timingMode );
protected long nextIteration( boolean force )
{
if ( t0 == -1L )
t0 = timer.getNanoseconds();
this.gameNanoTime = ( now - t0 );
final long gameTime = getGameNanoTime();
t03 = timer.getNanoseconds();
final long nanoDivisor = getTimingMode().getNanoDivisor();
if ( force || ( getPauseMode() != PAUSE_TOTAL ) )
{
update( gameTime / nanoDivisor, bruttoFrameTime / nanoDivisor, getTimingMode() );
}
now = timer.getNanoseconds();
if ( ( getFPSLimiter() != null ) && ( minItTime != 0L ) )
{
getFPSLimiter().limitFPS( getIterationsCount(), now - t03, getMinIterationTime(), getTimer() );
}
now = timer.getNanoseconds();
bruttoFrameTime = now - t02;
t02 = now;
iterations++;
return ( bruttoFrameTime );
}
protected final boolean isStopping()
{
return ( isStopping );
}
/**
* This method defines the main loop of the Thread.<br>
* Override it to change its behavior.
*/
protected void loop()
{
while ( !isStopping() )
{
nextIteration( false );
}
}
/**
* {@inheritDoc}
*/
public void run()
{
t0 = timer.getNanoseconds();
t02 = t0;
now = t0;
gameNanoTime = 0L;
bruttoFrameTime = 0L;
iterations = 0L;
isStopping = false;
pauseMode = PAUSE_NONE;
try
{
loop();
}
catch ( Throwable t )
{
t.printStackTrace();
this.end();
}
this.thread = null;
}
/**
* @return the Thread, that runs this loop.
*/
public Thread getThread()
{
return ( thread );
}
/**
* @return true, if this thread is running.
*/
public boolean isRunning()
{
return ( getThread() != null );
}
protected void begin( boolean startNewThread )
{
if ( getThread() == null )
{
if ( startNewThread )
{
this.thread = new Thread( this , "xith3d updating" );
this.thread.start();
}
else
{
this.thread = Thread.currentThread();
run();
}
}
else
{
throw new IllegalStateException( "This Thread is already running." );
}
}
public void end()
{
if ( getThread() != null )
{
this.isStopping = true;
}
else
{
throw new IllegalStateException( "This Thread is not running." );
}
}
public UpdatingThread( long minItTime, GameTimeHost gameTimeHost )
{
this.setMinIterationTime( minItTime );
this.gameTimeHost = gameTimeHost;
}
public UpdatingThread( long minItTime )
{
this( minItTime, null );
}
public UpdatingThread( GameTimeHost gameTimeHost )
{
this( 0L, gameTimeHost );
}
public UpdatingThread()
{
this( 0L, null );
}
}