allow simulation delays below real-time

This commit is contained in:
fros4943 2009-07-06 12:29:57 +00:00
parent 9aee8dbc16
commit 28976d9e15
2 changed files with 214 additions and 149 deletions

View File

@ -24,7 +24,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* *
* $Id: Simulation.java,v 1.50 2009/07/03 13:37:41 fros4943 Exp $ * $Id: Simulation.java,v 1.51 2009/07/06 12:29:57 fros4943 Exp $
*/ */
package se.sics.cooja; package se.sics.cooja;
@ -59,7 +59,8 @@ 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; private int delayTime=0, delayPeriod=1;
private long delayLastSim;
private long currentSimulationTime = 0; private long currentSimulationTime = 0;
@ -179,13 +180,35 @@ 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) {
/*logger.info("Delay at: " + t);*/ /* As fast as possible: no need to reschedule delay event */
if (delayTime == 0) { if (delayTime == 0) {
return; return;
} }
try { Thread.sleep(delayTime); } catch (InterruptedException e) { } /* Special case: real time */
scheduleEventUnsafe(this, t+MILLISECOND); if (delayPeriod == Integer.MIN_VALUE) {
delayLastSim++;
long tmp = System.currentTimeMillis();
if (delayLastSim > tmp) {
try {
Thread.sleep(delayLastSim-tmp);
} catch (InterruptedException e) {
}
}
/* Reschedule us next millisecond */
scheduleEventUnsafe(this, t+MILLISECOND);
return;
}
/* Normal operation */
try {
Thread.sleep(delayTime);
} catch (InterruptedException e) {
}
/* Reschedule us next period */
scheduleEventUnsafe(this, t+delayPeriod*MILLISECOND);
} }
public String toString() { public String toString() {
return "DELAY"; return "DELAY";
@ -227,6 +250,7 @@ public class Simulation extends Observable implements Runnable {
isRunning = true; isRunning = true;
/* Schedule tick events */ /* Schedule tick events */
delayLastSim = System.currentTimeMillis();
scheduleEventUnsafe(tickEmulatedMotesEvent, currentSimulationTime); scheduleEventUnsafe(tickEmulatedMotesEvent, currentSimulationTime);
scheduleEventUnsafe(delayEvent, currentSimulationTime - (currentSimulationTime % 1000) + 1000); scheduleEventUnsafe(delayEvent, currentSimulationTime - (currentSimulationTime % 1000) + 1000);
scheduleEventUnsafe(millisecondEvent, currentSimulationTime - (currentSimulationTime % 1000) + 1000); scheduleEventUnsafe(millisecondEvent, currentSimulationTime - (currentSimulationTime % 1000) + 1000);
@ -424,7 +448,7 @@ public class Simulation extends Observable implements Runnable {
// Delay time // Delay time
element = new Element("delaytime"); element = new Element("delaytime");
element.setText(Integer.toString(delayTime)); element.setText("" + getDelayTime());
config.add(element); config.add(element);
// Random seed // Random seed
@ -505,7 +529,7 @@ public class Simulation extends Observable implements Runnable {
// Delay time // Delay time
if (element.getName().equals("delaytime")) { if (element.getName().equals("delaytime")) {
delayTime = Integer.parseInt(element.getText()); setDelayTime(Integer.parseInt(element.getText()));
} }
// Random seed // Random seed
@ -768,26 +792,57 @@ public class Simulation extends Observable implements Runnable {
} }
/** /**
* Set delay time in milliseconds. * Set delay time (ms).
* The simulation loop sleeps this value every simulated millisecond. * The simulation loop delays given value every simulated millisecond.
* If the value is zero there is no delay.
* If the value is negative, the simulation loop delays 1ms every (-time) simulated milliseconds.
* *
* @param delayTime New delay time (ms) * 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 delayTime) { public void setDelayTime(int time) {
this.delayTime = delayTime; 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 */
}
rescheduleEvents = true; rescheduleEvents = true;
this.setChanged(); this.setChanged();
this.notifyObservers(this); this.notifyObservers(this);
} }
/** /**
* Returns current delay time. * Returns current delay time value.
* Note that this value can be negative.
* *
* @return Delay time (ms) * @see #setDelayTime(int)
* @return Delay time value. May be negative, see {@link #setDelayTime(int)}
*/ */
public int getDelayTime() { public int getDelayTime() {
/* Special case: real time */
if (delayPeriod == Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
if (delayPeriod > 1) {
return -delayPeriod;
}
return delayTime; return delayTime;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2006, Swedish Institute of Computer Science. * Copyright (c) 2009, 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
@ -26,7 +26,7 @@
* 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.14 2009/05/26 14:27:00 fros4943 Exp $ * $Id: SimControl.java,v 1.15 2009/07/06 12:29:57 fros4943 Exp $
*/ */
package se.sics.cooja.plugins; package se.sics.cooja.plugins;
@ -40,31 +40,32 @@ import java.util.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.Timer; import javax.swing.Timer;
import javax.swing.event.*; import javax.swing.event.*;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import se.sics.cooja.*; import se.sics.cooja.*;
/** /**
* The Control Panel is a simple control panel for simulations. * 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("Control Panel")
@PluginType(PluginType.SIM_STANDARD_PLUGIN) @PluginType(PluginType.SIM_STANDARD_PLUGIN)
public class SimControl extends VisPlugin { public class SimControl extends VisPlugin {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 8452253637624664192L;
private static Logger logger = Logger.getLogger(SimControl.class); 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 = 921; // e^9.21 => ~10000 private static final int SLIDE_MAX = 1000;
private static final int TIME_MAX = 10000;
private static final int LABEL_UPDATE_INTERVAL = 100; private static final int LABEL_UPDATE_INTERVAL = 100;
private JButton startButton, stopButton;
private JSlider sliderDelay; private JSlider sliderDelay;
private JLabel simulationTime, delayLabel; private JLabel simulationTime, delayLabel;
private JButton startButton, stopButton;
private JFormattedTextField stopTimeTextField; private JFormattedTextField stopTimeTextField;
private Observer simObserver; private Observer simObserver;
@ -72,53 +73,19 @@ public class SimControl extends VisPlugin {
/** /**
* Create a new simulation control panel. * Create a new simulation control panel.
* *
* @param simulationToControl Simulation to control * @param simulation Simulation to control
*/ */
public SimControl(Simulation simulationToControl, GUI gui) { public SimControl(Simulation simulation, GUI gui) {
super("Control Panel - " + simulationToControl.getTitle(), gui); super("Control Panel", gui);
this.simulation = simulation;
simulation = simulationToControl;
JButton button;
JPanel smallPanel;
/* Update current time label when simulation is running */ /* Update current time label when simulation is running */
if (simulation.isRunning()) { if (simulation.isRunning()) {
updateLabelTimer.start(); updateLabelTimer.start();
} }
/* Observe current simulation */ /* Container */
simulation.addObserver(simObserver = new Observer() { JPanel smallPanel;
public void update(Observable obs, Object obj) {
if (simulation.isRunning()) {
startButton.setEnabled(false);
stopButton.setEnabled(true);
/* Start label update timer */
if (!updateLabelTimer.isRunning()) {
updateLabelTimer.start();
}
} else {
startButton.setEnabled(true);
stopButton.setEnabled(false);
/* Update simulation stop text field */
if (!stopEvent.isScheduled()) {
stopTimeTextField.setValue(simulation.getSimulationTimeMillis());
}
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
sliderDelay.setValue(convertTimeToSlide(simulation.getDelayTime()));
simulationTime.setText("Current simulation time: " + simulation.getSimulationTimeMillis());
simulationTime.setToolTipText("Simulation time in microseconds: " + simulation.getSimulationTime());
}
});
}
});
// Main panel
JPanel controlPanel = new JPanel(); JPanel controlPanel = new JPanel();
controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS)); controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
@ -129,22 +96,9 @@ public class SimControl extends VisPlugin {
smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS));
smallPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5)); smallPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
button = new JButton("Start"); smallPanel.add(startButton = new JButton(startAction));
button.setActionCommand("start"); smallPanel.add(stopButton = new JButton(stopAction));
button.addActionListener(myEventHandler); smallPanel.add(new JButton(stepAction));
startButton = button;
smallPanel.add(button);
button = new JButton("Pause");
button.setActionCommand("stop");
button.addActionListener(myEventHandler);
stopButton = button;
smallPanel.add(button);
button = new JButton("Step millisecond");
button.setActionCommand("single_ms");
button.addActionListener(myEventHandler);
smallPanel.add(button);
smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
controlPanel.add(smallPanel); controlPanel.add(smallPanel);
@ -168,17 +122,16 @@ public class SimControl extends VisPlugin {
stopEvent.remove(); stopEvent.remove();
} }
JFormattedTextField numberTextField = (JFormattedTextField) e.getSource(); long t = ((Number)e.getNewValue()).intValue()*Simulation.MILLISECOND;
long stopTime = ((Number) numberTextField.getValue()).intValue()*Simulation.MILLISECOND; if (t <= SimControl.this.simulation.getSimulationTime()) {
if (stopTime <= simulation.getSimulationTime()) {
/* No simulation stop scheduled */ /* No simulation stop scheduled */
numberTextField.setBackground(Color.LIGHT_GRAY); stopTimeTextField.setBackground(Color.LIGHT_GRAY);
numberTextField.setToolTipText("Enter future simulation time"); stopTimeTextField.setToolTipText("Enter simulation time when to automatically pause");
} else { } else {
/* Schedule simulation stop */ /* Schedule simulation stop */
numberTextField.setBackground(Color.WHITE); stopTimeTextField.setBackground(Color.WHITE);
numberTextField.setToolTipText("Simulation will stop at time (us): " + stopTime); stopTimeTextField.setToolTipText("Simulation will stop at time (us): " + t);
simulation.scheduleEvent(stopEvent, stopTime); SimControl.this.simulation.scheduleEvent(stopEvent, t);
} }
} }
}); });
@ -196,7 +149,7 @@ public class SimControl extends VisPlugin {
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("Current simulation time: " + simulation.getSimulationTimeMillis()); label = new JLabel("?");
smallPanel.add(label); smallPanel.add(label);
simulationTime = label; simulationTime = label;
@ -208,11 +161,7 @@ public class SimControl extends VisPlugin {
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));
if (simulation.getDelayTime() > 0) { label = new JLabel("?");
label = new JLabel("Delay: " + simulation.getDelayTime() + " ms");
} else {
label = new JLabel("No simulation delay");
}
smallPanel.add(label); smallPanel.add(label);
delayLabel = label; delayLabel = label;
@ -224,77 +173,111 @@ public class SimControl extends VisPlugin {
smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS));
smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5)); smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5));
JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, SLIDE_MAX, convertTimeToSlide(simulation.getDelayTime())); sliderDelay = new JSlider(
slider.addChangeListener(myEventHandler); 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();
}
});
Hashtable labelTable = new Hashtable(); smallPanel.add(sliderDelay);
for (int i=0; i < 100; i += 10) {
labelTable.put(new Integer(convertTimeToSlide(i)), new JLabel("."));
}
for (int i=200; i < 10000; i += 500) {
labelTable.put(new Integer(convertTimeToSlide(i)), new JLabel(":"));
}
slider.setLabelTable(labelTable);
slider.setPaintLabels(true);
smallPanel.add(slider);
sliderDelay = slider;
smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
controlPanel.add(smallPanel); controlPanel.add(smallPanel);
pack(); /* Observe current simulation */
simulation.addObserver(simObserver = new Observer() {
public void update(Observable obs, Object obj) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
updateValues();
}
});
}
});
/* Set initial values */
updateValues();
try { pack();
setSelected(true); }
} catch (java.beans.PropertyVetoException e) {
// Could not select 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 */
simulationTime.setText("Current simulation time: "
+ simulation.getSimulationTimeMillis()
+ " ms");
if (simulation.isRunning() && !updateLabelTimer.isRunning()) {
updateLabelTimer.start();
}
if (!simulation.isRunning()) {
simulationTime.setToolTipText("Simulation time in microseconds: "
+ simulation.getSimulationTime());
}
/* Update control buttons */
if (simulation.isRunning()) {
startAction.setEnabled(false);
stopAction.setEnabled(true);
stepAction.setEnabled(false);
} else {
startAction.setEnabled(true);
stopAction.setEnabled(false);
stepAction.setEnabled(true);
if (!stopEvent.isScheduled()) {
stopTimeTextField.setValue(simulation.getSimulationTimeMillis());
}
} }
} }
private int convertSlideToTime(int slide) { private int convertSlideToTime(int slide) {
if (slide == SLIDE_MAX) { if (slide == SLIDE_MIN) {
return TIME_MAX; /* Special case: no delay */
return 0;
} }
return (int) Math.round(Math.exp(slide/100.0) - 1.0); 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) { private int convertTimeToSlide(int time) {
if (time == TIME_MAX) { if (time == 0) {
return SLIDE_MAX; /* Special case: no delay */
return SLIDE_MIN;
} }
if (time == Integer.MIN_VALUE) {
return (int) Math.round((Math.log(time + 1)*100.0)); /* Special case: real time */
return SLIDE_MIN+1;
}
if (time < 0) {
return time+2; /* Ignore special cases */
}
return time;
} }
private class MyEventHandler implements ActionListener, ChangeListener {
public void stateChanged(ChangeEvent e) {
if (e.getSource() == sliderDelay) {
simulation.setDelayTime(convertSlideToTime(sliderDelay.getValue()));
if (simulation.getDelayTime() > 0) {
delayLabel.setText("Delay: " + simulation.getDelayTime() + " ms");
} else {
delayLabel.setText("No simulation delay");
}
} else {
logger.debug("Unhandled state change: " + e);
}
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("start")) {
simulation.startSimulation();
} else if (e.getActionCommand().equals("stop")) {
if (simulation.isRunning()) {
simulation.stopSimulation();
}
} else if (e.getActionCommand().equals("single_ms")) {
simulation.stepMillisecondSimulation();
} else {
logger.debug("Unhandled action: " + e.getActionCommand());
}
}
} MyEventHandler myEventHandler = new MyEventHandler();
public void closePlugin() { public void closePlugin() {
/* Remove simulation observer */ /* Remove simulation observer */
if (simObserver != null) { if (simObserver != null) {
@ -314,12 +297,21 @@ public class SimControl extends VisPlugin {
public void execute(long t) { public void execute(long t) {
/* Stop simulation */ /* Stop simulation */
simulation.stopSimulation(); 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("Current simulation time: " + simulation.getSimulationTimeMillis()); simulationTime.setText("Current simulation time: "
+ simulation.getSimulationTimeMillis()
+ " ms");
/* Automatically stop if simulation is no longer running */ /* Automatically stop if simulation is no longer running */
if (!simulation.isRunning()) { if (!simulation.isRunning()) {
@ -327,4 +319,22 @@ public class SimControl extends VisPlugin {
} }
} }
}); });
private Action startAction = new AbstractAction("Start") {
public void actionPerformed(ActionEvent e) {
simulation.startSimulation();
stopButton.requestFocus();
}
};
private Action stopAction = new AbstractAction("Pause") {
public void actionPerformed(ActionEvent e) {
simulation.stopSimulation();
startButton.requestFocus();
}
};
private Action stepAction = new AbstractAction("Step millisecond") {
public void actionPerformed(ActionEvent e) {
simulation.stepMillisecondSimulation();
}
};
} }