/**
* 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.gamedata;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.jar.JarFile;
import net.ctdp.rfdynhud.input.InputAction;
import net.ctdp.rfdynhud.input.InputMapping;
import net.ctdp.rfdynhud.input.InputMappingsManager;
import net.ctdp.rfdynhud.input.KnownInputActions;
import net.ctdp.rfdynhud.plugins.GameEventsPlugin;
import net.ctdp.rfdynhud.render.WidgetsManager;
import net.ctdp.rfdynhud.render.__RenderPrivilegedAccess;
import net.ctdp.rfdynhud.util.RFDHLog;
import net.ctdp.rfdynhud.widgets.WidgetsConfiguration;
import net.ctdp.rfdynhud.widgets.base.widget.Widget;
import net.ctdp.rfdynhud.widgets.base.widget.__WPrivilegedAccess;
import org.jagatoo.util.classes.ClassSearcher;
import org.jagatoo.util.classes.SuperClassCriterium;
/**
* Dispatches game and game data events.
*
* @author Marvin Froehlich (CTDP)
*/
class GameEventsDispatcher
{
private WidgetsConfiguration widgetsConfig = null;
private final GameEventsPlugin[] plugins;
/**
*
* @param widgetsConfig
*/
public void setWidgetsConfiguration( WidgetsConfiguration widgetsConfig )
{
this.widgetsConfig = widgetsConfig;
}
/**
* @param eventsManager
* @param isEditorMode
* @param gameData
* @param renderListenerManager
*/
public void fireOnStarted( GameEventsManager eventsManager, LiveGameData gameData, boolean isEditorMode, WidgetsManager renderListenerManager )
{
if ( plugins != null )
{
for ( int i = 0; i < plugins.length; i++ )
{
try
{
plugins[i].onPluginStarted( eventsManager, gameData, isEditorMode, renderListenerManager );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
}
/**
*
* @param manager
* @param gameData
* @param widgetsConfig
*/
public void fireBeforeWidgetsConfigurationCleared( WidgetsManager manager, LiveGameData gameData, WidgetsConfiguration widgetsConfig )
{
__RenderPrivilegedAccess.fireBeforeWidgetsConfigurationCleared( manager, gameData, widgetsConfig );
}
/**
*
* @param manager
* @param gameData
* @param widgetsConfig
*/
public void fireAfterWidgetsConfigurationLoaded( WidgetsManager manager, LiveGameData gameData, WidgetsConfiguration widgetsConfig )
{
__RenderPrivilegedAccess.fireAfterWidgetsConfigurationLoaded( manager, gameData, widgetsConfig );
}
/**
* This method is executed when a new track was loaded.<br>
* <br>
* Calls {@link Widget#onTrackChanged(String, LiveGameData, boolean)} on each Widget.
*
* @param trackname the track's name
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnTrackChanged( String trackname, LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onTrackChanged( trackname, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onTrackChanged( trackname, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
private final List<Widget> waitingWidgets = new ArrayList<Widget>();
public final boolean hasWaitingWidgets()
{
return ( !waitingWidgets.isEmpty() );
}
/**
* This method is called when a new session was started.<br>
* <br>
* Calls {@link Widget#onSessionStarted(SessionType, LiveGameData, boolean)} on each Widget.
*
* @param sessionType the current session type
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnSessionStarted( SessionType sessionType, LiveGameData gameData, boolean isEditorMode )
{
waitingWidgets.clear();
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
try
{
widget.onSessionStarted( sessionType, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
waitingWidgets.add( widget );
}
}
/**
* This method is called when a the user entered realtime mode.<br>
* <br>
* Calls {@link Widget#onCockpitEntered(LiveGameData, boolean)} on each Widget.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnCockpitEntered( LiveGameData gameData, boolean isEditorMode )
{
waitingWidgets.clear();
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
try
{
widget.onCockpitEntered( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
waitingWidgets.add( widget );
}
}
/**
* This method is called when a the user entered realtime mode.<br>
* <br>
* Calls {@link Widget#onCockpitEntered(LiveGameData, boolean)} on each Widget.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void checkAndFireOnNeededDataComplete( LiveGameData gameData, boolean isEditorMode )
{
if ( waitingWidgets.size() == 0 )
return;
for ( int i = waitingWidgets.size() - 1; i >= 0; i-- )
{
Widget widget = waitingWidgets.get( i );
int neededData = ( widget.getNeededData() & Widget.NEEDED_DATA_ALL );
if ( ( ( neededData & Widget.NEEDED_DATA_TELEMETRY ) != 0 ) && gameData.getTelemetryData().isUpdatedInTimeScope() )
neededData &= ~Widget.NEEDED_DATA_TELEMETRY;
if ( ( ( neededData & Widget.NEEDED_DATA_SCORING ) != 0 ) && !gameData.getScoringInfo().isUpdatedInTimeScope() )
neededData &= ~Widget.NEEDED_DATA_SCORING;
//if ( ( ( neededData & Widget.NEEDED_DATA_SETUP ) != 0 ) && !gameData.getSetup().isUpdatedInTimeScope() )
// neededData &= ~Widget.NEEDED_DATA_SETUP;
if ( neededData == 0 )
{
try
{
widget.onNeededDataComplete( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
waitingWidgets.remove( i );
widget.forceReinitialization();
widget.forceCompleteRedraw( true );
}
}
}
/**
* This method is called when a the user exited the garage.<br>
* <br>
* Calls {@link Widget#onPitsEntered(LiveGameData, boolean)} on each Widget.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnPitsEntered( LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onPitsEntered( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onPitsEntered( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when a the user entered the garage.<br>
* <br>
* Calls {@link Widget#onGarageEntered(LiveGameData, boolean)} on each Widget.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnGarageEntered( LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onGarageEntered( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onGarageEntered( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when a the user exited the garage.<br>
* <br>
* Calls {@link Widget#onGarageExited(LiveGameData, boolean)} on each Widget.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnGarageExited( LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onGarageExited( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onGarageExited( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when a the user exited the garage.<br>
* <br>
* Calls {@link Widget#onPitsExited(LiveGameData, boolean)} on each Widget.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnPitsExited( LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onPitsExited( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onPitsExited( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when a the user exited the cockpit.<br>
* <br>
* Calls {@link Widget#onCockpitExited(LiveGameData, boolean)} on each Widget.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnCockpitExited( LiveGameData gameData, boolean isEditorMode )
{
waitingWidgets.clear();
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onCockpitExited( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when {@link ScoringInfo} have been updated (done at 2Hz).
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnScoringInfoUpdated( LiveGameData gameData, boolean isEditorMode )
{
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onScoringInfoUpdated( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when {@link VehiclePhysics} have been updated.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnVehiclePhysicsUpdated( LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onVehiclePhysicsUpdated( gameData );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
}
/**
* This method is called when {@link VehicleSetup} has been updated.
*
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnVehicleSetupUpdated( LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onVehicleSetupUpdated( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
Widget widget = widgetsConfig.getWidget( i );
try
{
widget.forceAndSetDirty( true );
widget.onVehicleSetupUpdated( gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when either the player's vehicle control has changed or another vehicle is being viewed.
*
* @param viewedVSI the viewed vehicle
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnVehicleControlChanged( VehicleScoringInfo viewedVSI, LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onVehicleControlChanged( viewedVSI, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
__WPrivilegedAccess.onVehicleControlChanged( widgetsConfig.getWidget( i ), viewedVSI, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is called when a lap has been finished and a new one was started.
*
* @param vsi the vehicle
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnLapStarted( VehicleScoringInfo vsi, LiveGameData gameData, boolean isEditorMode )
{
if ( gameData.gameEventsListeners != null )
{
for ( int i = 0; i < gameData.gameEventsListeners.length; i++ )
{
try
{
gameData.gameEventsListeners[i].onLapStarted( vsi, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
final int n = widgetsConfig.getNumWidgets();
for ( int i = 0; i < n; i++ )
{
try
{
widgetsConfig.getWidget( i ).onLapStarted( vsi, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
/**
* This method is fired by the {@link InputMappingsManager},
* if the state of a bound input component has changed.
*
* @param mapping the input mapping
* @param state the current state of the input device component
* @param modifierMask the current key modifier mask
* @param when the timestamp of the input action in nano seconds
* @param gameData the live game data
* @param isEditorMode <code>true</code>, if the Editor is used for rendering instead of rFactor
*/
public void fireOnInputStateChanged( InputMapping mapping, boolean state, int modifierMask, long when, LiveGameData gameData, boolean isEditorMode )
{
Widget widget = widgetsConfig.getWidget( mapping.getWidgetName() );
if ( widget == null )
return;
InputAction action = mapping.getAction();
try
{
if ( action == KnownInputActions.ToggleWidgetVisibility )
__WPrivilegedAccess.toggleInputVisible( widget );
else
__WPrivilegedAccess.onBoundInputStateChanged( widget, action, state, modifierMask, when, gameData, isEditorMode );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
/**
* @param eventsManager
* @param isEditorMode
* @param gameData
* @param renderListenerManager
*/
public void fireOnShutdown( GameEventsManager eventsManager, LiveGameData gameData, boolean isEditorMode, WidgetsManager renderListenerManager )
{
if ( plugins != null )
{
for ( int i = plugins.length - 1; i >= 0; i-- )
{
try
{
plugins[i].onPluginShutdown( eventsManager, gameData, isEditorMode, renderListenerManager );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
}
private static void findPluginJars( File folder, List<URL> jars )
{
for ( File f : folder.listFiles() )
{
if ( f.isDirectory() )
{
if ( !f.getName().equals( ".svn" ) )
findPluginJars( f, jars );
}
else if ( f.getName().toLowerCase().endsWith( ".jar" ) )
{
try
{
jars.add( f.toURI().toURL() );
}
catch ( Throwable t )
{
RFDHLog.exception( t );
}
}
}
}
private static URL[] findPluginJars( File pluginsFolder )
{
ArrayList<URL> jars = new ArrayList<URL>();
for ( File f : pluginsFolder.listFiles() )
{
if ( f.isDirectory() && !f.getName().equals( ".svn" ) )
{
findPluginJars( f, jars );
}
}
URL[] urls = new URL[ jars.size() ];
return ( jars.toArray( urls ) );
}
private static final GameEventsPlugin[] findPlugins( GameFileSystem fileSystem )
{
RFDHLog.printlnEx( "Loading GameEventsPlugins..." );
File pluginsFolder = fileSystem.getSubPluginsFolder();
HashMap<Class<?>, JarFile> jarMap = new HashMap<Class<?>, JarFile>();
List<Class<?>> pluginClasses;
if ( ( pluginsFolder != null ) && pluginsFolder.exists() )
{
URLClassLoader classLoader = new URLClassLoader( findPluginJars( pluginsFolder ), GameEventsPlugin.class.getClassLoader() );
try
{
pluginClasses = ClassSearcher.findClasses( true, classLoader, jarMap, new SuperClassCriterium( GameEventsPlugin.class, false ) );
}
catch ( IOException e )
{
RFDHLog.exception( e );
pluginClasses = null;
}
}
else
{
pluginClasses = ClassSearcher.findClasses( jarMap, new SuperClassCriterium( GameEventsPlugin.class, false ) );
}
if ( ( pluginClasses == null ) || ( pluginClasses.size() == 0 ) )
{
RFDHLog.printlnEx( "No plugin found." );
return ( null );
}
GameEventsPlugin[] plugins = new GameEventsPlugin[ pluginClasses.size() ];
for ( int i = 0; i < pluginClasses.size(); i++ )
{
Class<?> pluginClazz = pluginClasses.get( i );
JarFile jar = jarMap.get( pluginClazz );
File baseFolder = ( jar == null ) ? null : new File( jar.getName() ).getParentFile();
try
{
plugins[i] = (GameEventsPlugin)pluginClazz.getConstructor( File.class ).newInstance( baseFolder );
RFDHLog.printlnEx( " Found plugin \"" + pluginClazz.getName() + "\"." );
}
catch ( Throwable t )
{
RFDHLog.exception( "WARNING: Couldn't instantiate GameEventsPlugin \"" + pluginClazz.getName() + "\"." );
RFDHLog.exception( t );
}
}
RFDHLog.printlnEx( "Found and initialized " + plugins.length + " plugin" + ( plugins.length == 1 ? "" : "s" ) + "." );
return ( plugins );
}
private GameEventsDispatcher( GameEventsPlugin[] plugins )
{
this.plugins = plugins;
}
public static GameEventsDispatcher createGameEventsDispatcher( GameFileSystem fileSystem )
{
return ( new GameEventsDispatcher( findPlugins( fileSystem ) ) );
}
}