replaced Simulation's setDelayTime(ms) method with more intuitive setSpeedLimit(ratio)

updated Simulation Control tool to use new setSpeedLimit(), and removed unused "run until" function

updated references to old setDelayTime(ms)
This commit is contained in:
Fredrik Osterlind 2012-06-04 16:14:05 +02:00
parent 92765b384e
commit bc0727a931
6 changed files with 207 additions and 308 deletions

View file

@ -161,8 +161,8 @@ while (currentMote <= NR_MOTES) {
GENERATE_MSG(1000, "continue"); GENERATE_MSG(1000, "continue");
WAIT_UNTIL(msg.equals("continue")); WAIT_UNTIL(msg.equals("continue"));
/* override simulation delay to realtime */ /* override simulation speed limit to realtime */
mote.getSimulation().setDelayTime(java.lang.Integer.MIN_VALUE); mote.getSimulation().setSpeedLimit(1.0);
/* ping motes */ /* ping motes */
currentMote = 1; currentMote = 1;

View file

@ -133,8 +133,8 @@ pingOnceProcess = new java.lang.Runtime.getRuntime().exec(pingOnceCmd);
GENERATE_MSG(5000, "continue"); GENERATE_MSG(5000, "continue");
WAIT_UNTIL(msg.equals("continue")); WAIT_UNTIL(msg.equals("continue"));
/* override simulation delay to realtime */ /* override simulation speed limit to realtime */
mote.getSimulation().setDelayTime(java.lang.Integer.MIN_VALUE); mote.getSimulation().setSpeedLimit(1.0);
/* start ping process */ /* start ping process */
var runnableObj = new Object(); var runnableObj = new Object();

View file

@ -34,6 +34,7 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Container; import java.awt.Container;
import java.awt.Dialog; import java.awt.Dialog;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Frame; import java.awt.Frame;
import java.awt.GraphicsDevice; import java.awt.GraphicsDevice;
@ -41,7 +42,6 @@ import java.awt.GraphicsEnvironment;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Window; import java.awt.Window;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter; import java.awt.event.ComponentAdapter;
@ -109,8 +109,8 @@ import javax.swing.KeyStroke;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager; import javax.swing.ToolTipManager;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.MenuEvent; import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener; import javax.swing.event.MenuListener;
import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileFilter;
@ -682,7 +682,7 @@ public class GUI extends Observable {
} }
private JMenuBar createMenuBar() { private JMenuBar createMenuBar() {
JMenuItem menuItem; JMenuItem menuItem;
/* Prepare GUI actions */ /* Prepare GUI actions */
@ -706,20 +706,20 @@ public class GUI extends Observable {
/* toolsMenu = new JMenu("Tools"); */ /* toolsMenu = new JMenu("Tools"); */
JMenu settingsMenu = new JMenu("Settings"); JMenu settingsMenu = new JMenu("Settings");
JMenu helpMenu = new JMenu("Help"); JMenu helpMenu = new JMenu("Help");
menuBar.add(fileMenu); menuBar.add(fileMenu);
menuBar.add(simulationMenu); menuBar.add(simulationMenu);
menuBar.add(motesMenu); menuBar.add(motesMenu);
menuBar.add(toolsMenu); menuBar.add(toolsMenu);
menuBar.add(settingsMenu); menuBar.add(settingsMenu);
menuBar.add(helpMenu); menuBar.add(helpMenu);
fileMenu.setMnemonic(KeyEvent.VK_F); fileMenu.setMnemonic(KeyEvent.VK_F);
simulationMenu.setMnemonic(KeyEvent.VK_S); simulationMenu.setMnemonic(KeyEvent.VK_S);
motesMenu.setMnemonic(KeyEvent.VK_M); motesMenu.setMnemonic(KeyEvent.VK_M);
toolsMenu.setMnemonic(KeyEvent.VK_T); toolsMenu.setMnemonic(KeyEvent.VK_T);
helpMenu.setMnemonic(KeyEvent.VK_H); helpMenu.setMnemonic(KeyEvent.VK_H);
/* File menu */ /* File menu */
fileMenu.addMenuListener(new MenuListener() { fileMenu.addMenuListener(new MenuListener() {
public void menuSelected(MenuEvent e) { public void menuSelected(MenuEvent e) {
@ -732,7 +732,7 @@ public class GUI extends Observable {
public void menuCanceled(MenuEvent e) { public void menuCanceled(MenuEvent e) {
} }
}); });
fileMenu.add(new JMenuItem(newSimulationAction)); fileMenu.add(new JMenuItem(newSimulationAction));
menuOpenSimulation = new JMenu("Open simulation"); menuOpenSimulation = new JMenu("Open simulation");
@ -865,9 +865,9 @@ public class GUI extends Observable {
public void menuCanceled(MenuEvent e) { public void menuCanceled(MenuEvent e) {
} }
}); });
// Mote menu // Mote menu
motesMenu.addMenuListener(new MenuListener() { motesMenu.addMenuListener(new MenuListener() {
@ -879,7 +879,7 @@ public class GUI extends Observable {
public void menuCanceled(MenuEvent e) { public void menuCanceled(MenuEvent e) {
} }
}); });
// Mote types sub menu // Mote types sub menu
menuMoteTypes = new JMenu("Add motes"); menuMoteTypes = new JMenu("Add motes");
@ -889,10 +889,10 @@ public class GUI extends Observable {
// Clear menu // Clear menu
menuMoteTypes.removeAll(); menuMoteTypes.removeAll();
if (mySimulation != null) { if (mySimulation != null) {
// Recreate menu items // Recreate menu items
JMenuItem menuItem; JMenuItem menuItem;
@ -904,13 +904,13 @@ public class GUI extends Observable {
menuItem.addActionListener(guiEventHandler); menuItem.addActionListener(guiEventHandler);
menuMoteTypes.add(menuItem); menuMoteTypes.add(menuItem);
} }
if(mySimulation.getMoteTypes().length > 0) { if(mySimulation.getMoteTypes().length > 0) {
menuMoteTypes.add(new JSeparator()); menuMoteTypes.add(new JSeparator());
} }
} }
menuMoteTypes.add(menuMoteTypeClasses); menuMoteTypes.add(menuMoteTypeClasses);
} }
@ -928,10 +928,10 @@ public class GUI extends Observable {
menuItem.putClientProperty("class", MoteTypeInformation.class); menuItem.putClientProperty("class", MoteTypeInformation.class);
motesMenu.add(menuItem); motesMenu.add(menuItem);
motesMenu.add(new JMenuItem(removeAllMotesAction)); motesMenu.add(new JMenuItem(removeAllMotesAction));
// Tools menu // Tools menu
toolsMenu.addMenuListener(new MenuListener() { toolsMenu.addMenuListener(new MenuListener() {
public void menuSelected(MenuEvent e) { public void menuSelected(MenuEvent e) {
for (Component menuComponent: toolsMenu.getMenuComponents()) { for (Component menuComponent: toolsMenu.getMenuComponents()) {
@ -1028,7 +1028,7 @@ public class GUI extends Observable {
settingsMenu.add(new JMenuItem(showBufferSettingsAction)); settingsMenu.add(new JMenuItem(showBufferSettingsAction));
/* Help */ /* Help */
helpMenu.add(new JMenuItem(showGettingStartedAction)); helpMenu.add(new JMenuItem(showGettingStartedAction));
helpMenu.add(new JMenuItem(showKeyboardShortcutsAction)); helpMenu.add(new JMenuItem(showKeyboardShortcutsAction));
JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(showQuickHelpAction); JCheckBoxMenuItem checkBox = new JCheckBoxMenuItem(showQuickHelpAction);
@ -1175,7 +1175,7 @@ public class GUI extends Observable {
ToolTipManager.sharedInstance().setDismissDelay(60000); ToolTipManager.sharedInstance().setDismissDelay(60000);
/* Nimbus */ /* Nimbus */
try { try {
String osName = System.getProperty("os.name").toLowerCase(); String osName = System.getProperty("os.name").toLowerCase();
if (osName.startsWith("linux")) { if (osName.startsWith("linux")) {
@ -1186,10 +1186,10 @@ public class GUI extends Observable {
break; break;
} }
} }
} catch (UnsupportedLookAndFeelException e) { } catch (UnsupportedLookAndFeelException e) {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} }
} else { } else {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} }
@ -3211,7 +3211,7 @@ public class GUI extends Observable {
logger.fatal("Error: " + e.getMessage(), e); logger.fatal("Error: " + e.getMessage(), e);
System.exit(1); System.exit(1);
} }
sim.setDelayTime(0); sim.setSpeedLimit(null);
sim.startSimulation(); sim.startSimulation();
} else { } else {
logger.fatal("No test editor controlling simulation, aborting"); logger.fatal("No test editor controlling simulation, aborting");
@ -4499,7 +4499,7 @@ public class GUI extends Observable {
super.setEnabled(newValue); super.setEnabled(newValue);
} }
public boolean shouldBeEnabled() { public boolean shouldBeEnabled() {
return getSimulation() != null; return getSimulation() != null && getSimulation().isRunnable();
} }
}; };
class StartPluginGUIAction extends GUIAction { class StartPluginGUIAction extends GUIAction {

View file

@ -63,8 +63,13 @@ public class Simulation extends Observable implements Runnable {
private Vector<MoteType> moteTypes = new Vector<MoteType>(); private Vector<MoteType> moteTypes = new Vector<MoteType>();
private int delayTime=0, delayPeriod=1; /* If true, run simulation at full speed */
private long delayLastSim; private boolean speedLimitNone = true;
/* Limit simulation speed to maxSpeed; if maxSpeed is 1.0 simulation is run at real-time speed */
private double speedLimit;
/* Used to restrict simulation speed */
private long speedLimitLastSimtime;
private long speedLimitLastRealtime;
private long currentSimulationTime = 0; private long currentSimulationTime = 0;
@ -191,35 +196,32 @@ public class Simulation extends Observable implements Runnable {
private TimeEvent delayEvent = new TimeEvent(0) { private TimeEvent delayEvent = new TimeEvent(0) {
public void execute(long t) { public void execute(long t) {
/* As fast as possible: no need to reschedule delay event */ if (speedLimitNone) {
if (delayTime == 0) { /* As fast as possible: no need to reschedule delay event */
return; return;
} }
/* Special case: real time */ long diffSimtime = (getSimulationTime() - speedLimitLastSimtime)/1000; /* ms */
if (delayPeriod == Integer.MIN_VALUE) { long diffRealtime = System.currentTimeMillis() - speedLimitLastRealtime; /* ms */
delayLastSim++; long expectedDiffRealtime = (long) (diffSimtime/speedLimit);
long tmp = System.currentTimeMillis(); long sleep = expectedDiffRealtime - diffRealtime;
if (delayLastSim > tmp) { if (sleep >= 0) {
try { /* Slow down simulation */
Thread.sleep(delayLastSim-tmp); try {
} catch (InterruptedException e) { Thread.sleep(sleep);
} } catch (InterruptedException e) {
} }
/* Reschedule us next millisecond */
scheduleEvent(this, t+MILLISECOND); scheduleEvent(this, t+MILLISECOND);
return; } else {
/* Reduce slow-down: execute this delay event less often */
scheduleEvent(this, t-sleep*MILLISECOND);
} }
/* Normal operation */ /* Update counters every second */
try { if (diffRealtime > 1000) {
Thread.sleep(delayTime); speedLimitLastRealtime = System.currentTimeMillis();
} catch (InterruptedException e) { speedLimitLastSimtime = getSimulationTime();
} }
/* Reschedule us next period */
scheduleEvent(this, t+delayPeriod*MILLISECOND);
} }
public String toString() { public String toString() {
return "DELAY"; return "DELAY";
@ -249,7 +251,8 @@ public class Simulation extends Observable implements Runnable {
long lastStartTime = System.currentTimeMillis(); long lastStartTime = System.currentTimeMillis();
logger.info("Simulation main loop started, system time: " + lastStartTime); logger.info("Simulation main loop started, system time: " + lastStartTime);
isRunning = true; isRunning = true;
delayLastSim = System.currentTimeMillis(); speedLimitLastRealtime = System.currentTimeMillis();
speedLimitLastSimtime = getSimulationTime();
/* Simulation starting */ /* Simulation starting */
this.setChanged(); this.setChanged();
@ -473,10 +476,12 @@ public class Simulation extends Observable implements Runnable {
element.setText(title); element.setText(title);
config.add(element); config.add(element);
// Delay time /* Max simulation speed */
element = new Element("delaytime"); if (!speedLimitNone) {
element.setText("" + getDelayTime()); element = new Element("speedlimit");
config.add(element); element.setText("" + getSpeedLimit());
config.add(element);
}
// Random seed // Random seed
element = new Element("randomseed"); element = new Element("randomseed");
@ -560,9 +565,14 @@ public class Simulation extends Observable implements Runnable {
title = element.getText(); title = element.getText();
} }
// Delay time /* Max simulation speed */
if (element.getName().equals("delaytime")) { if (element.getName().equals("speedlimit")) {
setDelayTime(Integer.parseInt(element.getText())); String text = element.getText();
if (text.equals("null")) {
setSpeedLimit(null);
} else {
setSpeedLimit(Double.parseDouble(text));
}
} }
// Random seed // Random seed
@ -942,42 +952,27 @@ public class Simulation extends Observable implements Runnable {
} }
/** /**
* Set delay time (ms). * Limit simulation speed to given ratio.
* The simulation loop delays given value every simulated millisecond. * This method may be called from outside the simulation thread.
* If the value is zero there is no delay. * @param newSpeedLimit
* If the value is negative, the simulation loop delays 1ms every (-time) simulated milliseconds.
*
* Examples:
* time=0: no sleeping (simulation runs as fast as possible).
* time=10: simulation delays 10ms every simulated millisecond.
* time=-5: simulation delays 1ms every 5 simulated milliseconds.
*
* Special case:
* time=Integer.MIN_VALUE: simulation tries to execute at real time.
*
* @param time New delay time value
*/ */
public void setDelayTime(int time) { public void setSpeedLimit(final Double newSpeedLimit) {
if (time == Integer.MIN_VALUE) {
/* Special case: real time */
delayTime = Integer.MIN_VALUE;
delayPeriod = Integer.MIN_VALUE;
delayLastSim = System.currentTimeMillis();
} else if (time < 0) {
delayTime = 1;
delayPeriod = -time;
} else {
delayTime = time;
delayPeriod = 1; /* minimum */
}
invokeSimulationThread(new Runnable() { invokeSimulationThread(new Runnable() {
public void run() { public void run() {
if (!delayEvent.isScheduled()) { if (newSpeedLimit == null) {
scheduleEvent( speedLimitNone = true;
delayEvent, return;
currentSimulationTime - (currentSimulationTime % MILLISECOND) + MILLISECOND);
} }
speedLimitNone = false;
speedLimitLastRealtime = System.currentTimeMillis();
speedLimitLastSimtime = getSimulationTime();
speedLimit = newSpeedLimit.doubleValue();
if (delayEvent.isScheduled()) {
delayEvent.remove();
}
scheduleEvent(delayEvent, currentSimulationTime);
Simulation.this.setChanged(); Simulation.this.setChanged();
Simulation.this.notifyObservers(this); Simulation.this.notifyObservers(this);
} }
@ -985,23 +980,13 @@ public class Simulation extends Observable implements Runnable {
} }
/** /**
* Returns current delay time value. * @return Max simulation speed ratio. Returns null if no limit.
* Note that this value can be negative.
*
* @see #setDelayTime(int)
* @return Delay time value. May be negative, see {@link #setDelayTime(int)}
*/ */
public int getDelayTime() { public Double getSpeedLimit() {
/* Special case: real time */ if (speedLimitNone) {
if (delayPeriod == Integer.MIN_VALUE) { return null;
return Integer.MIN_VALUE;
} }
return new Double(speedLimit);
if (delayPeriod > 1) {
return -delayPeriod;
}
return delayTime;
} }
/** /**
@ -1087,9 +1072,12 @@ public class Simulation extends Observable implements Runnable {
* @return True if simulation is runnable * @return True if simulation is runnable
*/ */
public boolean isRunnable() { public boolean isRunnable() {
return motes.size() > 0; if (motes.isEmpty()) {
return false;
}
return isRunning || hasPollRequests || eventQueue.peekFirst() != null;
} }
/** /**
* Get current simulation title (short description). * Get current simulation title (short description).
* *
@ -1108,5 +1096,4 @@ public class Simulation extends Observable implements Runnable {
public void setTitle(String title) { public void setTitle(String title) {
this.title = title; this.title = title;
} }
} }

View file

@ -132,16 +132,16 @@ public class ScriptRunner extends VisPlugin {
JMenu fileMenu = new JMenu("File"); JMenu fileMenu = new JMenu("File");
JMenu editMenu = new JMenu("Edit"); JMenu editMenu = new JMenu("Edit");
JMenu runMenu = new JMenu("Run"); JMenu runMenu = new JMenu("Run");
menuBar.add(fileMenu); menuBar.add(fileMenu);
menuBar.add(editMenu); menuBar.add(editMenu);
menuBar.add(runMenu); menuBar.add(runMenu);
this.setJMenuBar(menuBar); this.setJMenuBar(menuBar);
/* Examples popup menu */ /* Examples popup menu */
JMenu examplesMenu = new JMenu("Load example script"); JMenu examplesMenu = new JMenu("Load example script");
for (int i=0; i < EXAMPLE_SCRIPTS.length; i += 2) { for (int i=0; i < EXAMPLE_SCRIPTS.length; i += 2) {
final String file = EXAMPLE_SCRIPTS[i]; final String file = EXAMPLE_SCRIPTS[i];
JMenuItem exampleItem = new JMenuItem(EXAMPLE_SCRIPTS[i+1]); JMenuItem exampleItem = new JMenuItem(EXAMPLE_SCRIPTS[i+1]);
@ -161,7 +161,7 @@ public class ScriptRunner extends VisPlugin {
} }
fileMenu.add(examplesMenu); fileMenu.add(examplesMenu);
{ {
/* Workaround to configure jsyntaxpane */ /* Workaround to configure jsyntaxpane */
JEditorPane e = new JEditorPane(); JEditorPane e = new JEditorPane();
@ -242,7 +242,7 @@ public class ScriptRunner extends VisPlugin {
JPanel buttonPanel = new JPanel(new BorderLayout()); JPanel buttonPanel = new JPanel(new BorderLayout());
/*buttonPanel.add(BorderLayout.CENTER, toggleButton);*/ /*buttonPanel.add(BorderLayout.CENTER, toggleButton);*/
/* buttonPanel.add(BorderLayout.EAST, runTestButton);*/ /* buttonPanel.add(BorderLayout.EAST, runTestButton);*/
JPanel southPanel = new JPanel(new BorderLayout()); JPanel southPanel = new JPanel(new BorderLayout());
@ -663,7 +663,7 @@ public class ScriptRunner extends VisPlugin {
} catch (Exception e) { } catch (Exception e) {
logger.fatal("Error: " + e.getMessage(), e); logger.fatal("Error: " + e.getMessage(), e);
} }
simulation.setDelayTime(0); simulation.setSpeedLimit(null);
simulation.startSimulation(); simulation.startSimulation();
} }
return true; return true;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009, Swedish Institute of Computer Science. * Copyright (c) 2012, Swedish Institute of Computer Science.
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -25,70 +25,53 @@
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE. * SUCH DAMAGE.
*
* $Id: SimControl.java,v 1.18 2010/11/03 12:29:47 adamdunkels Exp $
*/ */
package se.sics.cooja.plugins; package se.sics.cooja.plugins;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JSlider; import javax.swing.JRadioButtonMenuItem;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.Timer; import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.log4j.Logger;
import se.sics.cooja.ClassDescription; import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI; import se.sics.cooja.GUI;
import se.sics.cooja.HasQuickHelp; import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.PluginType; import se.sics.cooja.PluginType;
import se.sics.cooja.Simulation; import se.sics.cooja.Simulation;
import se.sics.cooja.TimeEvent;
import se.sics.cooja.VisPlugin; import se.sics.cooja.VisPlugin;
/** /**
* Control panel for starting and pausing the current simulation. * Control panel for starting and pausing the current simulation.
* Allows for configuring the simulation delay.
* *
* @author Fredrik Osterlind * @author Fredrik Osterlind
*/ */
@ClassDescription("Control Panel") @ClassDescription("Simulation control...")
@PluginType(PluginType.SIM_STANDARD_PLUGIN) @PluginType(PluginType.SIM_STANDARD_PLUGIN)
public class SimControl extends VisPlugin implements HasQuickHelp { public class SimControl extends VisPlugin implements HasQuickHelp {
private static final long serialVersionUID = 8452253637624664192L; private static final int LABEL_UPDATE_INTERVAL = 150;
private static Logger logger = Logger.getLogger(SimControl.class);
private Simulation simulation; private Simulation simulation;
private static final int SLIDE_MIN = -100;
private static final int SLIDE_MAX = 1000;
private static final int LABEL_UPDATE_INTERVAL = 100;
private JButton startButton, stopButton; private JButton startButton, stopButton;
private JSlider sliderDelay; private JLabel simulationTime, simulationSpeedup;
private JLabel simulationTime, simulationSpeedup, delayLabel;
private JFormattedTextField stopTimeTextField;
private Observer simObserver; private Observer simObserver;
@ -101,7 +84,7 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
* @param simulation Simulation to control * @param simulation Simulation to control
*/ */
public SimControl(Simulation simulation, GUI gui) { public SimControl(Simulation simulation, GUI gui) {
super("Control Panel", gui); super("Simulation control", gui);
this.simulation = simulation; this.simulation = simulation;
/* Update current time label when simulation is running */ /* Update current time label when simulation is running */
@ -109,6 +92,54 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
updateLabelTimer.start(); updateLabelTimer.start();
} }
/* Menus */
JMenuBar menuBar = new JMenuBar();
JMenu runMenu = new JMenu("Run");
JMenu speedMenu = new JMenu("Speed limit");
menuBar.add(runMenu);
menuBar.add(speedMenu);
this.setJMenuBar(menuBar);
runMenu.add(new JMenuItem(startAction));
runMenu.add(new JMenuItem(stopAction));
runMenu.add(new JMenuItem(stepAction));
runMenu.add(new JMenuItem(reloadAction));
ButtonGroup speedlimitButtonGroup = new ButtonGroup();
JRadioButtonMenuItem limitMenuItemNo = new JRadioButtonMenuItem(
new ChangeMaxSpeedLimitAction("No speed limit", null));
speedlimitButtonGroup.add(limitMenuItemNo);
speedMenu.add(limitMenuItemNo);
JRadioButtonMenuItem limitMenuItem1 = new JRadioButtonMenuItem(
new ChangeMaxSpeedLimitAction("1%", 0.01));
speedlimitButtonGroup.add(limitMenuItem1);
speedMenu.add(limitMenuItem1);
JRadioButtonMenuItem limitMenuItem2 = new JRadioButtonMenuItem(
new ChangeMaxSpeedLimitAction("10%", 0.10));
speedlimitButtonGroup.add(limitMenuItem2);
speedMenu.add(limitMenuItem2);
JRadioButtonMenuItem limitMenuItem3 = new JRadioButtonMenuItem(
new ChangeMaxSpeedLimitAction("100%", 1.0));
speedlimitButtonGroup.add(limitMenuItem3);
speedMenu.add(limitMenuItem3);
JRadioButtonMenuItem limitMenuItem4 = new JRadioButtonMenuItem(
new ChangeMaxSpeedLimitAction("1000%", 10.0));
speedlimitButtonGroup.add(limitMenuItem4);
speedMenu.add(limitMenuItem4);
if (simulation.getSpeedLimit() == null) {
limitMenuItemNo.setSelected(true);
} else if (simulation.getSpeedLimit().doubleValue() == 0.01) {
limitMenuItem1.setSelected(true);
} else if (simulation.getSpeedLimit().doubleValue() == 0.10) {
limitMenuItem2.setSelected(true);
} else if (simulation.getSpeedLimit().doubleValue() == 1.0) {
limitMenuItem3.setSelected(true);
} else if (simulation.getSpeedLimit().doubleValue() == 10) {
limitMenuItem4.setSelected(true);
}
/* Container */ /* Container */
JPanel smallPanel; JPanel smallPanel;
JPanel controlPanel = new JPanel(); JPanel controlPanel = new JPanel();
@ -129,51 +160,6 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
controlPanel.add(smallPanel); controlPanel.add(smallPanel);
/* Run until */
smallPanel = new JPanel();
smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS));
smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
JLabel label = new JLabel("Stop at:");
smallPanel.add(label);
smallPanel.add(Box.createHorizontalStrut(10));
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
stopTimeTextField = new JFormattedTextField(integerFormat);
stopTimeTextField.addPropertyChangeListener("value", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
/* Remove already scheduled stop event */
if (stopEvent.isScheduled()) {
stopEvent.remove();
}
final long t = ((Number)e.getNewValue()).intValue()*Simulation.MILLISECOND;
if (t <= SimControl.this.simulation.getSimulationTime()) {
/* No simulation stop scheduled */
stopTimeTextField.setBackground(Color.LIGHT_GRAY);
stopTimeTextField.setToolTipText("Enter simulation time when to automatically pause");
} else {
/* Schedule simulation stop */
stopTimeTextField.setBackground(Color.WHITE);
stopTimeTextField.setToolTipText("Simulation will stop at time (us): " + t);
SimControl.this.simulation.invokeSimulationThread(new Runnable() {
public void run() {
if (stopEvent.isScheduled()) {
stopEvent.remove();
}
SimControl.this.simulation.scheduleEvent(stopEvent, t);
}
});
}
}
});
stopTimeTextField.setValue(simulation.getSimulationTimeMillis());
stopTimeTextField.setSize(100, stopTimeTextField.getHeight());
smallPanel.add(stopTimeTextField);
smallPanel.add(Box.createHorizontalGlue());
smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
controlPanel.add(smallPanel); controlPanel.add(smallPanel);
@ -182,7 +168,7 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS));
smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
label = new JLabel("?"); JLabel label = new JLabel("?");
smallPanel.add(label); smallPanel.add(label);
simulationTime = label; simulationTime = label;
@ -201,41 +187,6 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
controlPanel.add(smallPanel); controlPanel.add(smallPanel);
/* Delay label */
smallPanel = new JPanel();
smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS));
smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
label = new JLabel("?");
smallPanel.add(label);
delayLabel = label;
smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
controlPanel.add(smallPanel);
/* Delay slider */
smallPanel = new JPanel();
smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS));
smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5));
sliderDelay = new JSlider(
JSlider.HORIZONTAL,
SLIDE_MIN,
SLIDE_MAX,
convertTimeToSlide(simulation.getDelayTime()));
sliderDelay.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
SimControl.this.simulation.setDelayTime(
convertSlideToTime(sliderDelay.getValue()));
updateValues();
}
});
smallPanel.add(sliderDelay);
smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
controlPanel.add(smallPanel);
/* Observe current simulation */ /* Observe current simulation */
simulation.addObserver(simObserver = new Observer() { simulation.addObserver(simObserver = new Observer() {
public void update(Observable obs, Object obj) { public void update(Observable obs, Object obj) {
@ -253,33 +204,31 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
this.lastSystemTimeTimestamp = System.currentTimeMillis(); this.lastSystemTimeTimestamp = System.currentTimeMillis();
this.lastSimulationTimeTimestamp = 0; this.lastSimulationTimeTimestamp = 0;
/* XXX HACK: here we set the position and size of the window when it
* appears on a blank simulation screen. */
this.setLocation(400, 0);
this.setSize(280, 160);
}
private class ChangeMaxSpeedLimitAction extends AbstractAction {
private Double maxSpeed;
public ChangeMaxSpeedLimitAction(String name, Double maxSpeed) {
super(name);
this.maxSpeed = maxSpeed;
}
public void actionPerformed(ActionEvent e) {
simulation.setSpeedLimit(maxSpeed);
}
} }
private void updateValues() { private void updateValues() {
/* Update simulation delay */
sliderDelay.setValue(convertTimeToSlide(simulation.getDelayTime()));
if (simulation.getDelayTime() == 0) {
delayLabel.setText("No simulation delay");
} else if (simulation.getDelayTime() == Integer.MIN_VALUE) {
delayLabel.setText("Real time");
} else if (simulation.getDelayTime() > 0) {
delayLabel.setText("Delay: " + simulation.getDelayTime() + " ms");
} else {
delayLabel.setText("Delay: 1/" + (-simulation.getDelayTime()) + " ms");
}
/* Update current time */ /* Update current time */
simulationTime.setText("Simulation time: " simulationTime.setText(getTimeString());
+ simulation.getSimulationTimeMillis() simulationSpeedup.setText("Speed: ---");
+ " ms");
simulationSpeedup.setText("Relative speed: ---");
if (simulation.isRunning() && !updateLabelTimer.isRunning()) { if (simulation.isRunning() && !updateLabelTimer.isRunning()) {
updateLabelTimer.start(); updateLabelTimer.start();
} }
if (!simulation.isRunning()) {
simulationTime.setToolTipText("Simulation time in microseconds: "
+ simulation.getSimulationTime());
}
/* Update control buttons */ /* Update control buttons */
if (simulation.isRunning()) { if (simulation.isRunning()) {
@ -287,44 +236,34 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
stopAction.setEnabled(true); stopAction.setEnabled(true);
stepAction.setEnabled(false); stepAction.setEnabled(false);
} else { } else {
startAction.setEnabled(true); if(simulation.isRunnable()) {
stopAction.setEnabled(false); startAction.setEnabled(true);
stepAction.setEnabled(true); stepAction.setEnabled(true);
} else {
if (!stopEvent.isScheduled()) { startAction.setEnabled(false);
stopTimeTextField.setValue(simulation.getSimulationTimeMillis()); stepAction.setEnabled(false);
} }
stopAction.setEnabled(false);
} }
} }
private int convertSlideToTime(int slide) { private static final long TIME_SECOND = 1000*Simulation.MILLISECOND;
if (slide == SLIDE_MIN) { private static final long TIME_MINUTE = 60*TIME_SECOND;
/* Special case: no delay */ private static final long TIME_HOUR = 60*TIME_MINUTE;
return 0; public String getTimeString() {
long t = simulation.getSimulationTime();
long h = (t / TIME_HOUR);
t -= (t / TIME_HOUR)*TIME_HOUR;
long m = (t / TIME_MINUTE);
t -= (t / TIME_MINUTE)*TIME_MINUTE;
long s = (t / TIME_SECOND);
t -= (t / TIME_SECOND)*TIME_SECOND;
long ms = t / Simulation.MILLISECOND;
if (h > 0) {
return String.format("Time: %d:%02d:%02d.%03d", h,m,s,ms);
} else {
return String.format("Time: %02d:%02d.%03d", m,s,ms);
} }
if (slide == SLIDE_MIN+1) {
/* Special case: real time */
return Integer.MIN_VALUE;
}
if (slide <= 0) {
return slide-2; /* Ignore special cases */
}
return slide;
}
private int convertTimeToSlide(int time) {
if (time == 0) {
/* Special case: no delay */
return SLIDE_MIN;
}
if (time == Integer.MIN_VALUE) {
/* Special case: real time */
return SLIDE_MIN+1;
}
if (time < 0) {
return time+2; /* Ignore special cases */
}
return time;
} }
public void closePlugin() { public void closePlugin() {
@ -333,46 +272,23 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
simulation.deleteObserver(simObserver); simulation.deleteObserver(simObserver);
} }
/* Remove stop event */
if (stopEvent.isScheduled()) {
stopEvent.remove();
}
/* Remove label update timer */ /* Remove label update timer */
updateLabelTimer.stop(); updateLabelTimer.stop();
} }
private TimeEvent stopEvent = new TimeEvent(0) {
public void execute(long t) {
/* Stop simulation */
simulation.stopSimulation();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
stopTimeTextField.setBackground(Color.LIGHT_GRAY);
stopTimeTextField.setToolTipText("Enter simulation time when to automatically pause");
stopTimeTextField.requestFocus();
}
});
}
};
private Timer updateLabelTimer = new Timer(LABEL_UPDATE_INTERVAL, new ActionListener() { private Timer updateLabelTimer = new Timer(LABEL_UPDATE_INTERVAL, new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
simulationTime.setText("Simulation time: " simulationTime.setText(getTimeString());
+ simulation.getSimulationTimeMillis()
+ " ms");
long systemTimeDiff = System.currentTimeMillis() - lastSystemTimeTimestamp; long systemTimeDiff = System.currentTimeMillis() - lastSystemTimeTimestamp;
if(systemTimeDiff > 1000) { if (systemTimeDiff > 1000) {
long simulationTimeDiff = simulation.getSimulationTimeMillis() - lastSimulationTimeTimestamp; long simulationTimeDiff = simulation.getSimulationTimeMillis() - lastSimulationTimeTimestamp;
lastSimulationTimeTimestamp = simulation.getSimulationTimeMillis(); lastSimulationTimeTimestamp = simulation.getSimulationTimeMillis();
lastSystemTimeTimestamp = System.currentTimeMillis(); lastSystemTimeTimestamp = System.currentTimeMillis();
// long String.format("%2.2f"
double speedup = (double)simulationTimeDiff / (double)systemTimeDiff; double speedup = (double)simulationTimeDiff / (double)systemTimeDiff;
simulationSpeedup.setText(String.format("Relative speed: %2.2f%%", 100 * speedup)); simulationSpeedup.setText(String.format("Speed: %2.2f%%", 100 * speedup));
} }
/* Automatically stop if simulation is no longer running */ /* Automatically stop if simulation is no longer running */
@ -413,10 +329,6 @@ public class SimControl extends VisPlugin implements HasQuickHelp {
"<p>The keyboard shortcut for starting and pausing the simulation is <i>Ctrl+S</i>. " + "<p>The keyboard shortcut for starting and pausing the simulation is <i>Ctrl+S</i>. " +
"<p><i>Step</i> runs the simulation for one millisecond. " + "<p><i>Step</i> runs the simulation for one millisecond. " +
"<p><i>Reload</i> reloads and restarts the simulation. " + "<p><i>Reload</i> reloads and restarts the simulation. " +
"<p>Writing simulation time in milliseconds in the <i>Stop at</i> field causes the simulation to pause at the given time. " + "<p>Simulation speed is controlled via the Speed limit menu.";
"<p>Simulation speed is controlled via the bottom slider. " +
"If the slider value is zero, simulation runs at full speed. " +
"<p>Setting the slider to <i>Real time</i>, simulation speed is capped to not run faster than real time. " +
"The <i>Real time</i> slider value is to the right of <i>No simulation delay</i>: click on the slider button and press the right arrow key on the keyboard. ";
} }
} }