--- /dev/null
+package net.mograsim.logic.core.timeline;
+
+import java.util.function.LongSupplier;
+
+public class PauseableTimeFunction implements LongSupplier
+{
+ private boolean paused = false;
+ private long unpausedSysTime = 0, lastPausedInternalTime = 0;
+ private int speedPercentage = 100;
+
+ public void pause()
+ {
+ if (!paused)
+ {
+ lastPausedInternalTime = getAsLong();
+ paused = true;
+ }
+ }
+
+ public void unpause()
+ {
+ if (paused)
+ {
+ paused = false;
+ unpausedSysTime = System.currentTimeMillis();
+ }
+ }
+
+ @Override
+ public long getAsLong()
+ {
+ return paused ? lastPausedInternalTime
+ : lastPausedInternalTime + ((System.currentTimeMillis() - unpausedSysTime) * speedPercentage) / 100;
+ }
+
+ public void setSpeedPercentage(int percentage)
+ {
+ if (!paused)
+ {
+ pause();
+ unpause();
+ }
+ this.speedPercentage = Integer.min(100, Integer.max(percentage, 1));
+ }
+
+ public boolean isPaused()
+ {
+ return paused;
+ }
+
+ public void toggle()
+ {
+ if (paused)
+ unpause();
+ else
+ pause();
+ }
+}
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import net.mograsim.logic.core.timeline.PauseableTimeFunction;
import net.mograsim.logic.core.timeline.Timeline;
//TODO maybe move to logic core?
private final AtomicBoolean shouldBeRunningLive;
private final AtomicBoolean isRunningLive;
+ private final AtomicBoolean isPaused;
private final AtomicLong nextExecSimulTime;
private final Thread simulationThread;
+ PauseableTimeFunction tf;
+
public LogicExecuter(Timeline timeline)
{
this.timeline = timeline;
- timeline.setTimeFunction(System::currentTimeMillis);
+ tf = new PauseableTimeFunction();
+ timeline.setTimeFunction(tf);
shouldBeRunningLive = new AtomicBoolean();
isRunningLive = new AtomicBoolean();
+ isPaused = new AtomicBoolean();
nextExecSimulTime = new AtomicLong();
simulationThread = new Thread(() ->
{
while (shouldBeRunningLive.get())
{
// always execute to keep timeline from "hanging behind" for too long
- long current = System.currentTimeMillis();
+ long current = tf.getAsLong();
timeline.executeUntil(timeline.laterThan(current), current + 10);
long sleepTime;
if (timeline.hasNext())
nextExecSimulTime.set(current + sleepTime);
if (sleepTime > 0)
Thread.sleep(sleepTime);
+
+ synchronized (isPaused)
+ {
+ while (isPaused.get())
+ isPaused.wait();
+ }
}
catch (@SuppressWarnings("unused") InterruptedException e)
{// do nothing; it is normal execution flow to be interrupted
waitForIsRunning(false);
}
+ public void unpauseLiveExecution()
+ {
+ synchronized (isPaused)
+ {
+ tf.unpause();
+ isPaused.set(false);
+ isPaused.notify();
+ }
+ }
+
+ public void pauseLiveExecution()
+ {
+ synchronized (isPaused)
+ {
+ tf.pause();
+ isPaused.set(true);
+ }
+ }
+
+ public boolean isPaused()
+ {
+ return isPaused.get();
+ }
+
+ public void setSpeedPercentage(int percentage)
+ {
+ tf.setSpeedPercentage(percentage);
+ }
+
private void waitForIsRunning(boolean expectedState)
{
while (isRunningLive.get() ^ expectedState)
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Slider;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
GridLayout layout = new GridLayout(1, true);
parent.setLayout(layout);
+ addSimulationControlWidgets(parent);
+
ui = new LogicUICanvas(parent, SWT.NONE, m.getModel());
ui.addTransformListener((x, y, z) -> part.setDirty(z < 1));
ZoomableCanvasUserInput userInput = new ZoomableCanvasUserInput(ui);
exec.startLiveExecution();
}
+ private void addSimulationControlWidgets(Composite parent)
+ {
+ Composite c = new Composite(parent, SWT.NONE);
+ c.setLayout(new GridLayout(4, false));
+ Button pauseButton = new Button(c, SWT.TOGGLE);
+ pauseButton.setText("Running");
+
+ pauseButton.addListener(SWT.Selection, e ->
+ {
+ if (!pauseButton.getSelection())
+ {
+ pauseButton.setText("Running");
+ exec.unpauseLiveExecution();
+ } else
+ {
+ pauseButton.setText("Paused");
+ exec.pauseLiveExecution();
+ }
+ });
+
+ Label speedLabel = new Label(c, SWT.NONE);
+ speedLabel.setText("Simulation Speed: ");
+
+ Slider slider = new Slider(c, SWT.NONE);
+ slider.setMinimum(1);
+ slider.setMaximum(100 + slider.getThumb());
+ slider.setIncrement(1);
+
+ Label speedPercentageLabel = new Label(c, SWT.NONE);
+ speedPercentageLabel.setText("100%");
+
+ slider.addListener(SWT.Selection, e ->
+ {
+ int selection = slider.getSelection();
+ speedPercentageLabel.setText(selection + "%");
+
+ exec.setSpeedPercentage(slider.getSelection());
+ });
+ slider.setSelection(100);
+
+ c.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.FILL_HORIZONTAL));
+ c.pack();
+ c.setVisible(true);
+ }
+
@Override
public void setFocus()
{