From 28976d9e152515e7486d2a457aff6c7661ac752c Mon Sep 17 00:00:00 2001 From: fros4943 Date: Mon, 6 Jul 2009 12:29:57 +0000 Subject: [PATCH] allow simulation delays below real-time --- .../cooja/java/se/sics/cooja/Simulation.java | 87 +++++- .../se/sics/cooja/plugins/SimControl.java | 276 +++++++++--------- 2 files changed, 214 insertions(+), 149 deletions(-) diff --git a/tools/cooja/java/se/sics/cooja/Simulation.java b/tools/cooja/java/se/sics/cooja/Simulation.java index 5f37a57ae..beb9503e9 100644 --- a/tools/cooja/java/se/sics/cooja/Simulation.java +++ b/tools/cooja/java/se/sics/cooja/Simulation.java @@ -24,7 +24,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * 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; @@ -59,7 +59,8 @@ public class Simulation extends Observable implements Runnable { private Vector moteTypes = new Vector(); - private int delayTime = 0; + private int delayTime=0, delayPeriod=1; + private long delayLastSim; private long currentSimulationTime = 0; @@ -179,13 +180,35 @@ public class Simulation extends Observable implements Runnable { private TimeEvent delayEvent = new TimeEvent(0) { public void execute(long t) { - /*logger.info("Delay at: " + t);*/ + /* As fast as possible: no need to reschedule delay event */ if (delayTime == 0) { return; } - try { Thread.sleep(delayTime); } catch (InterruptedException e) { } - scheduleEventUnsafe(this, t+MILLISECOND); + /* Special case: real time */ + 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() { return "DELAY"; @@ -227,6 +250,7 @@ public class Simulation extends Observable implements Runnable { isRunning = true; /* Schedule tick events */ + delayLastSim = System.currentTimeMillis(); scheduleEventUnsafe(tickEmulatedMotesEvent, currentSimulationTime); scheduleEventUnsafe(delayEvent, currentSimulationTime - (currentSimulationTime % 1000) + 1000); scheduleEventUnsafe(millisecondEvent, currentSimulationTime - (currentSimulationTime % 1000) + 1000); @@ -424,7 +448,7 @@ public class Simulation extends Observable implements Runnable { // Delay time element = new Element("delaytime"); - element.setText(Integer.toString(delayTime)); + element.setText("" + getDelayTime()); config.add(element); // Random seed @@ -505,7 +529,7 @@ public class Simulation extends Observable implements Runnable { // Delay time if (element.getName().equals("delaytime")) { - delayTime = Integer.parseInt(element.getText()); + setDelayTime(Integer.parseInt(element.getText())); } // Random seed @@ -768,26 +792,57 @@ public class Simulation extends Observable implements Runnable { } /** - * Set delay time in milliseconds. - * The simulation loop sleeps this value every simulated millisecond. + * Set delay time (ms). + * 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) { - this.delayTime = delayTime; - + public void setDelayTime(int time) { + 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; - this.setChanged(); 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() { + /* Special case: real time */ + if (delayPeriod == Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + + if (delayPeriod > 1) { + return -delayPeriod; + } + return delayTime; } diff --git a/tools/cooja/java/se/sics/cooja/plugins/SimControl.java b/tools/cooja/java/se/sics/cooja/plugins/SimControl.java index f8888a10b..65583fb43 100644 --- a/tools/cooja/java/se/sics/cooja/plugins/SimControl.java +++ b/tools/cooja/java/se/sics/cooja/plugins/SimControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, Swedish Institute of Computer Science. + * Copyright (c) 2009, Swedish Institute of Computer Science. * All rights reserved. * * 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 * 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; @@ -40,31 +40,32 @@ import java.util.*; import javax.swing.*; import javax.swing.Timer; import javax.swing.event.*; + import org.apache.log4j.Logger; 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 */ @ClassDescription("Control Panel") @PluginType(PluginType.SIM_STANDARD_PLUGIN) 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 Simulation simulation; - - private static final int SLIDE_MAX = 921; // e^9.21 => ~10000 - private static final int TIME_MAX = 10000; + 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 JSlider sliderDelay; private JLabel simulationTime, delayLabel; - private JButton startButton, stopButton; private JFormattedTextField stopTimeTextField; private Observer simObserver; @@ -72,53 +73,19 @@ public class SimControl extends VisPlugin { /** * Create a new simulation control panel. * - * @param simulationToControl Simulation to control + * @param simulation Simulation to control */ - public SimControl(Simulation simulationToControl, GUI gui) { - super("Control Panel - " + simulationToControl.getTitle(), gui); - - simulation = simulationToControl; - - JButton button; - JPanel smallPanel; + public SimControl(Simulation simulation, GUI gui) { + super("Control Panel", gui); + this.simulation = simulation; /* Update current time label when simulation is running */ if (simulation.isRunning()) { updateLabelTimer.start(); } - /* Observe current simulation */ - simulation.addObserver(simObserver = new Observer() { - 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 + /* Container */ + JPanel smallPanel; JPanel controlPanel = new JPanel(); 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.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5)); - button = new JButton("Start"); - button.setActionCommand("start"); - button.addActionListener(myEventHandler); - 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.add(startButton = new JButton(startAction)); + smallPanel.add(stopButton = new JButton(stopAction)); + smallPanel.add(new JButton(stepAction)); smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); controlPanel.add(smallPanel); @@ -168,17 +122,16 @@ public class SimControl extends VisPlugin { stopEvent.remove(); } - JFormattedTextField numberTextField = (JFormattedTextField) e.getSource(); - long stopTime = ((Number) numberTextField.getValue()).intValue()*Simulation.MILLISECOND; - if (stopTime <= simulation.getSimulationTime()) { + long t = ((Number)e.getNewValue()).intValue()*Simulation.MILLISECOND; + if (t <= SimControl.this.simulation.getSimulationTime()) { /* No simulation stop scheduled */ - numberTextField.setBackground(Color.LIGHT_GRAY); - numberTextField.setToolTipText("Enter future simulation time"); + stopTimeTextField.setBackground(Color.LIGHT_GRAY); + stopTimeTextField.setToolTipText("Enter simulation time when to automatically pause"); } else { /* Schedule simulation stop */ - numberTextField.setBackground(Color.WHITE); - numberTextField.setToolTipText("Simulation will stop at time (us): " + stopTime); - simulation.scheduleEvent(stopEvent, stopTime); + stopTimeTextField.setBackground(Color.WHITE); + stopTimeTextField.setToolTipText("Simulation will stop at time (us): " + t); + SimControl.this.simulation.scheduleEvent(stopEvent, t); } } }); @@ -196,7 +149,7 @@ public class SimControl extends VisPlugin { smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - label = new JLabel("Current simulation time: " + simulation.getSimulationTimeMillis()); + label = new JLabel("?"); smallPanel.add(label); simulationTime = label; @@ -208,11 +161,7 @@ public class SimControl extends VisPlugin { smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - if (simulation.getDelayTime() > 0) { - label = new JLabel("Delay: " + simulation.getDelayTime() + " ms"); - } else { - label = new JLabel("No simulation delay"); - } + label = new JLabel("?"); smallPanel.add(label); delayLabel = label; @@ -224,77 +173,111 @@ public class SimControl extends VisPlugin { smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5)); - JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, SLIDE_MAX, convertTimeToSlide(simulation.getDelayTime())); - slider.addChangeListener(myEventHandler); + 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(); + } + }); - Hashtable labelTable = new Hashtable(); - 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.add(sliderDelay); smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); 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 { - setSelected(true); - } catch (java.beans.PropertyVetoException e) { - // Could not select + pack(); + } + + 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) { - if (slide == SLIDE_MAX) { - return TIME_MAX; + if (slide == SLIDE_MIN) { + /* 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) { - if (time == TIME_MAX) { - return SLIDE_MAX; + if (time == 0) { + /* Special case: no delay */ + return SLIDE_MIN; } - - return (int) Math.round((Math.log(time + 1)*100.0)); + if (time == Integer.MIN_VALUE) { + /* 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() { /* Remove simulation observer */ if (simObserver != null) { @@ -314,12 +297,21 @@ public class SimControl extends VisPlugin { 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() { 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 */ 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(); + } + }; }