/** * Copyright (C) 2009-2014 Cars and Tracks Development Project (CTDP). * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package net.ctdp.rfdynhud.plugins.simulation; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.zip.GZIPInputStream; import org.jagatoo.util.streams.StreamUtils; import net.ctdp.rfdynhud.RFDynHUD; import net.ctdp.rfdynhud.gamedata.GameDataStreamSource; import net.ctdp.rfdynhud.gamedata.GameEventsManager; import net.ctdp.rfdynhud.gamedata.__GDPrivilegedAccess; /** * Capable of playing back recorded data events simulations. * * @author Marvin Froehlich (CTDP) */ public class SimulationPlayer { public static interface PlaybackControl { /** * Gets a time scale factor. A value <= 0 will make timing get totally ignored. * * @return a time scale factor. */ public float getTimeScale(); public void update(); public boolean isCancelled(); } public static class SimDataStreamSource implements GameDataStreamSource { private final InputStream in; @Override public InputStream getInputStreamForGraphicsInfo() { return ( in ); } @Override public InputStream getInputStreamForTelemetryData() { return ( in ); } @Override public InputStream getInputStreamForScoringInfo() { return ( in ); } @Override public InputStream getInputStreamForDrivingAids() { return ( in ); } @Override public InputStream getInputStreamForCommentaryRequestInfo() { return ( in ); } public SimDataStreamSource( InputStream in ) { this.in = in; } }; private static long waitForTimeCode( long t0, long t, PlaybackControl control ) throws InterruptedException { if ( ( control != null ) && control.isCancelled() ) return ( t ); if ( ( ( control == null ) || ( control.getTimeScale() > 0f ) ) && ( t0 != -1L ) ) { long waitTime = t - t0; if ( control != null ) { float timeScale = control.getTimeScale(); if ( timeScale != 1.0f ) waitTime = (long)( waitTime / timeScale ); } waitTime = Math.min( waitTime, 1000L ); while ( waitTime > 0L ) { if ( ( control != null ) && control.isCancelled() ) break; if ( waitTime > 100L ) { Thread.sleep( 100L ); waitTime -= 100L; } else { Thread.sleep( waitTime ); waitTime = 0L; } } } return ( t ); } public static void playback( GameEventsManager eventsManager, Object syncMonitor, File file, PlaybackControl control ) throws Throwable { boolean oldSimMode = __GDPrivilegedAccess.simulationMode; __GDPrivilegedAccess.simulationMode = true; if ( syncMonitor == null ) syncMonitor = new Object(); DataInputStream in = null; try { in = new DataInputStream( new BufferedInputStream( new GZIPInputStream( new FileInputStream( file ) ) ) ); SimDataStreamSource simUserObject = new SimDataStreamSource( in ); //LiveGameData gameData = eventsManager.getGameData(); boolean firstSessionStart = true; boolean isGraphicsReady = false; boolean isTelementryReady = false; boolean isScoringReady = false; boolean isWeatherReady = false; long t0 = -1L; int len = 0; int code = 0; while ( ( code = in.read() ) != -1 ) { if ( ( control != null ) && control.isCancelled() ) break; //System.out.println( "code: " + code + " ('" + (char)code + "')" ); //System.out.println( "waiting for data: " + eventsManager.getWaitingForData( true ) ); switch ( code ) { case SimulationConstants.ON_SESSION_STARTED: t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { if ( firstSessionStart ) eventsManager.onSessionEnded( simUserObject ); firstSessionStart = false; eventsManager.onSessionStarted( simUserObject ); } break; case SimulationConstants.ON_SESSION_ENDED: t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { eventsManager.onSessionEnded( simUserObject ); } break; case SimulationConstants.ON_PHYSICS: t0 = waitForTimeCode( t0, in.readLong(), control ); break; case SimulationConstants.ON_SETUP: t0 = waitForTimeCode( t0, in.readLong(), control ); break; case SimulationConstants.ON_TRACK: t0 = waitForTimeCode( t0, in.readLong(), control ); len = in.readShort(); for ( int i = 0; i < len; i++ ) in.readChar(); break; case SimulationConstants.ON_COCKPIT_ENTERED: t0 = waitForTimeCode( t0, in.readLong(), control ); t0 = -1L; // Avoid mysterious wait time! break; case SimulationConstants.ON_COCKPIT_EXITED: t0 = waitForTimeCode( t0, in.readLong(), control ); break; case SimulationConstants.ON_GARAGE_ENTERED: t0 = waitForTimeCode( t0, in.readLong(), control ); break; case SimulationConstants.ON_GARAGE_EXITED: t0 = waitForTimeCode( t0, in.readLong(), control ); break; case SimulationConstants.ON_PITS_ENTERED: t0 = waitForTimeCode( t0, in.readLong(), control ); break; case SimulationConstants.ON_PITS_EXITED: t0 = waitForTimeCode( t0, in.readLong(), control ); break; case SimulationConstants.ON_CONTROL: t0 = waitForTimeCode( t0, in.readLong(), control ); in.readShort(); break; case SimulationConstants.ON_LAP: //System.out.println( "lap" ); t0 = waitForTimeCode( t0, in.readLong(), control ); in.readShort(); break; case SimulationConstants.ON_DATA_UPDATED_DRIVING_AIDS: //System.out.println( "driving_aids" ); t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { eventsManager.onDrivingAidsUpdated( simUserObject ); } break; case SimulationConstants.ON_DATA_UPDATED_GRAPHICS: //System.out.println( "graphics" ); t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { eventsManager.onGraphicsInfoUpdated( simUserObject ); } isGraphicsReady = true; break; case SimulationConstants.ON_DATA_UPDATED_TELEMETRY: //System.out.println( "telemetry" ); t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { eventsManager.onTelemetryDataUpdated( simUserObject ); } isTelementryReady = true; break; case SimulationConstants.ON_DATA_UPDATED_SCORING: //System.out.println( "scoring" ); t0 = waitForTimeCode( t0, in.readLong(), control ); int numVehicles = in.readInt(); synchronized ( syncMonitor ) { eventsManager.onScoringInfoUpdated( numVehicles, simUserObject ); } isScoringReady = true; break; case SimulationConstants.ON_DATA_UPDATED_WEATHER: //System.out.println( "weather" ); t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { eventsManager.onWeatherInfoUpdated( simUserObject ); } isWeatherReady = true; break; case SimulationConstants.ON_DATA_UPDATED_COMMENTARY: //System.out.println( "commentary" ); t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { eventsManager.onCommentaryRequestInfoUpdated( simUserObject ); } break; case SimulationConstants.ON_VIEWPORT_CHANGED: t0 = waitForTimeCode( t0, in.readLong(), control ); in.readShort(); in.readShort(); in.readShort(); in.readShort(); break; case SimulationConstants.BEFORE_RENDERED: //System.out.println( "render" ); t0 = waitForTimeCode( t0, in.readLong(), control ); synchronized ( syncMonitor ) { eventsManager.beforeRender( (short)0, (short)0, (short)1920, (short)1080 ); } if ( isGraphicsReady && isTelementryReady && isScoringReady && isWeatherReady ) { if ( control != null ) control.update(); } break; default: throw new IllegalStateException( "Unexpected command value: " + code + " ('" + (char)code + "')" ); } } if ( !firstSessionStart ) eventsManager.onSessionEnded( simUserObject ); } finally { __GDPrivilegedAccess.simulationMode = oldSimMode; StreamUtils.closeStream( in ); } } public static void main( String[] args ) throws Throwable { boolean oldSimMode = __GDPrivilegedAccess.simulationMode; __GDPrivilegedAccess.simulationMode = true; try { final RFDynHUD rfDynHUD = RFDynHUD.createInstance( new net.ctdp.rfdynhud.gamedata.rfactor2._rf2_LiveGameDataObjectsFactory(), 1920, 1080 ); final GameEventsManager eventsManager = rfDynHUD.getEventsManager(); final long now = System.nanoTime(); eventsManager.onStartup( now ); eventsManager.onSessionStarted( now ); eventsManager.onCockpitEntered( now ); eventsManager.onCommentaryRequestInfoUpdated( null ); eventsManager.onGraphicsInfoUpdated( null ); eventsManager.beforeRender( (short)0, (short)0, (short)1920, (short)1080 ); //String file = "D:\\rfdynhud_data"; String file = "c:\\Spiele\\rFactor2\\Plugins\\rfDynHUD\\plugins\\simulation\\simdata"; SimulationPlayer.PlaybackControl control = new SimulationPlayer.PlaybackControl() { @Override public float getTimeScale() { return ( 0f ); } private boolean firstUpdate = true; @Override public void update() { if ( firstUpdate ) { //javax.swing.JOptionPane.showMessageDialog( null, "start" ); firstUpdate = false; } rfDynHUD.update(); } @Override public boolean isCancelled() { return ( false ); } }; SimulationPlayer.playback( eventsManager, null, new File( file ), control ); eventsManager.onShutdown( System.nanoTime() ); } finally { __GDPrivilegedAccess.simulationMode = oldSimMode; } } }