integrated sync branch

This commit is contained in:
hneemann 2016-07-10 10:27:21 +02:00
commit 07b730a87b
33 changed files with 755 additions and 366 deletions

View File

@ -31,7 +31,7 @@ This are the main features of Digital:
- You can test your design by creating test cases and execute them. - You can test your design by creating test cases and execute them.
- Many examples: From a transmission gate D-flipflop to a complete (simple) MIPS-like processor. - Many examples: From a transmission gate D-flipflop to a complete (simple) MIPS-like processor.
- Fast-run mode to perform a simulation without updating the HMI. - Fast-run mode to perform a simulation without updating the HMI.
A simple processor can be clocked at 3MHz. A simple processor can be clocked at 100kHz.
- Displaying a LST file when executing assembler programs within such a processor. - Displaying a LST file when executing assembler programs within such a processor.
- Simple remote TCP interface to allow e.g. an assembler IDE to control the simulator. - Simple remote TCP interface to allow e.g. an assembler IDE to control the simulator.
- SVG export of circuits, including a LaTeX-compatible SVG version (see [ctan](https://www.ctan.org/tex-archive/info/svg-inkscape)) - SVG export of circuits, including a LaTeX-compatible SVG version (see [ctan](https://www.ctan.org/tex-archive/info/svg-inkscape))
@ -105,7 +105,7 @@ Logisim works somewhat different, which sometimes leads to surprises like unexpe
If a complete processors is simulated, it is possible to calculate the simulation without an update of the If a complete processors is simulated, it is possible to calculate the simulation without an update of the
graphical representation. graphical representation.
A simple processor (see example) can so simulated with a roughly 3MHz clock (Intel® Core ™ i5-3230M CPU @ 2.60GHz) A simple processor (see example) can so simulated with a roughly 100kHz clock (Intel® Core ™ i5-3230M CPU @ 2.60GHz)
which is suitable also for more complex exercises. which is suitable also for more complex exercises.
There is a break gate having a single input. If this input changes from low to high this quick run is stopped. There is a break gate having a single input. If this input changes from low to high this quick run is stopped.
In this way, an assembler instruction BRK can be implemented, which then can be used to insert break points In this way, an assembler instruction BRK can be implemented, which then can be used to insert break points

View File

@ -26,7 +26,7 @@ Folgende Features zeichnen Digital aus:
- Analyse und Synthese von kombinatorischen Schaltungen und Schaltwerken. - Analyse und Synthese von kombinatorischen Schaltungen und Schaltwerken.
- Viele Beispiele: Vom Transmision-Gate D-FF bis zum kompletten MIPS-ähnlichem Prozessor. - Viele Beispiele: Vom Transmision-Gate D-FF bis zum kompletten MIPS-ähnlichem Prozessor.
- Fast-Run-Mode um eine Simulation ohne Aktualisierung des HMI durchzuführen. - Fast-Run-Mode um eine Simulation ohne Aktualisierung des HMI durchzuführen.
Ein einfacher Prozessor kann mit 3MHz getaktet werden. Ein einfacher Prozessor kann mit 100kHz getaktet werden.
- Anzeige von LST-Files bei der Ausführung von Assembler-Programmen. - Anzeige von LST-Files bei der Ausführung von Assembler-Programmen.
- Einfache Remote TCP-Schnittstelle um z.B. mit einer Assembler-IDE den Simulator zu steuern. - Einfache Remote TCP-Schnittstelle um z.B. mit einer Assembler-IDE den Simulator zu steuern.
- SVG-Export von Schaltungen, incl. einer LaTeX-tauglichen SVG-Variante (siehe [ctan](ftp://ftp.fau.de/ctan/info/svg-inkscape/InkscapePDFLaTeX.pdf)) - SVG-Export von Schaltungen, incl. einer LaTeX-tauglichen SVG-Variante (siehe [ctan](ftp://ftp.fau.de/ctan/info/svg-inkscape/InkscapePDFLaTeX.pdf))
@ -97,7 +97,7 @@ Logisim arbeitet hier etwas anders, was gelegentlich zu Überraschungen führt,
### Performance ### ### Performance ###
Werden komplette Prozessoren simuliert, ist es möglich die Simulation zu berechnen ohne die grafische Anzeige zu aktualisieren. Werden komplette Prozessoren simuliert, ist es möglich die Simulation zu berechnen ohne die grafische Anzeige zu aktualisieren.
Ein einfacher Prozessor (siehe Beispiel) lässt sich so mit etwa 3MHz takten (Intel® Core™ i5-3230M CPU @ 2.60GHz) was auch für Ein einfacher Prozessor (siehe Beispiel) lässt sich so mit etwa 100kHz takten (Intel® Core™ i5-3230M CPU @ 2.60GHz) was auch für
komplexere Übungen ausreichend ist. komplexere Übungen ausreichend ist.
Es gibt ein Break-Gatter welches einen einzelnen Eingang hat. Wechselt dieser Eingang von low auf high wird dieser Es gibt ein Break-Gatter welches einen einzelnen Eingang hat. Wechselt dieser Eingang von low auf high wird dieser
schnelle Lauf beendet. Auf diese Weise lässt sich eine Assembler-Anweisung BRK implementieren, womit sich dann Break-Points schnelle Lauf beendet. Auf diese Weise lässt sich eine Assembler-Anweisung BRK implementieren, womit sich dann Break-Points

View File

@ -14,7 +14,7 @@
<int>16</int> <int>16</int>
</entry> </entry>
</elementAttributes> </elementAttributes>
<pos x="180" y="220"/> <pos x="200" y="220"/>
</visualElement> </visualElement>
<visualElement> <visualElement>
<elementName>In</elementName> <elementName>In</elementName>
@ -24,7 +24,7 @@
<string>R</string> <string>R</string>
</entry> </entry>
</elementAttributes> </elementAttributes>
<pos x="180" y="460"/> <pos x="200" y="460"/>
</visualElement> </visualElement>
<visualElement> <visualElement>
<elementName>Comparator</elementName> <elementName>Comparator</elementName>
@ -101,7 +101,7 @@
<string>C</string> <string>C</string>
</entry> </entry>
</elementAttributes> </elementAttributes>
<pos x="180" y="540"/> <pos x="200" y="540"/>
</visualElement> </visualElement>
<visualElement> <visualElement>
<elementName>And</elementName> <elementName>And</elementName>
@ -160,6 +160,10 @@
<visualElement> <visualElement>
<elementName>D_FF</elementName> <elementName>D_FF</elementName>
<elementAttributes> <elementAttributes>
<entry>
<string>valueIsProbe</string>
<boolean>true</boolean>
</entry>
<entry> <entry>
<string>Label</string> <string>Label</string>
<string>Bank-FF</string> <string>Bank-FF</string>
@ -175,7 +179,7 @@
<string>W</string> <string>W</string>
</entry> </entry>
</elementAttributes> </elementAttributes>
<pos x="180" y="380"/> <pos x="200" y="380"/>
</visualElement> </visualElement>
<visualElement> <visualElement>
<elementName>Splitter</elementName> <elementName>Splitter</elementName>
@ -256,7 +260,7 @@
<p2 x="460" y="520"/> <p2 x="460" y="520"/>
</wire> </wire>
<wire> <wire>
<p1 x="180" y="460"/> <p1 x="200" y="460"/>
<p2 x="580" y="460"/> <p2 x="580" y="460"/>
</wire> </wire>
<wire> <wire>
@ -264,7 +268,7 @@
<p2 x="460" y="300"/> <p2 x="460" y="300"/>
</wire> </wire>
<wire> <wire>
<p1 x="220" y="300"/> <p1 x="240" y="300"/>
<p2 x="360" y="300"/> <p2 x="360" y="300"/>
</wire> </wire>
<wire> <wire>
@ -292,7 +296,7 @@
<p2 x="680" y="180"/> <p2 x="680" y="180"/>
</wire> </wire>
<wire> <wire>
<p1 x="220" y="180"/> <p1 x="240" y="180"/>
<p2 x="580" y="180"/> <p2 x="580" y="180"/>
</wire> </wire>
<wire> <wire>
@ -324,11 +328,11 @@
<p2 x="580" y="220"/> <p2 x="580" y="220"/>
</wire> </wire>
<wire> <wire>
<p1 x="180" y="220"/> <p1 x="200" y="220"/>
<p2 x="220" y="220"/> <p2 x="240" y="220"/>
</wire> </wire>
<wire> <wire>
<p1 x="220" y="220"/> <p1 x="240" y="220"/>
<p2 x="360" y="220"/> <p2 x="360" y="220"/>
</wire> </wire>
<wire> <wire>
@ -336,7 +340,7 @@
<p2 x="720" y="380"/> <p2 x="720" y="380"/>
</wire> </wire>
<wire> <wire>
<p1 x="180" y="380"/> <p1 x="200" y="380"/>
<p2 x="400" y="380"/> <p2 x="400" y="380"/>
</wire> </wire>
<wire> <wire>
@ -348,7 +352,7 @@
<p2 x="580" y="540"/> <p2 x="580" y="540"/>
</wire> </wire>
<wire> <wire>
<p1 x="180" y="540"/> <p1 x="200" y="540"/>
<p2 x="340" y="540"/> <p2 x="340" y="540"/>
</wire> </wire>
<wire> <wire>
@ -367,6 +371,14 @@
<p1 x="400" y="380"/> <p1 x="400" y="380"/>
<p2 x="400" y="560"/> <p2 x="400" y="560"/>
</wire> </wire>
<wire>
<p1 x="240" y="180"/>
<p2 x="240" y="220"/>
</wire>
<wire>
<p1 x="240" y="220"/>
<p2 x="240" y="300"/>
</wire>
<wire> <wire>
<p1 x="820" y="400"/> <p1 x="820" y="400"/>
<p2 x="820" y="600"/> <p2 x="820" y="600"/>
@ -395,14 +407,6 @@
<p1 x="680" y="180"/> <p1 x="680" y="180"/>
<p2 x="680" y="360"/> <p2 x="680" y="360"/>
</wire> </wire>
<wire>
<p1 x="220" y="180"/>
<p2 x="220" y="220"/>
</wire>
<wire>
<p1 x="220" y="220"/>
<p2 x="220" y="300"/>
</wire>
<wire> <wire>
<p1 x="540" y="520"/> <p1 x="540" y="520"/>
<p2 x="540" y="600"/> <p2 x="540" y="600"/>

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ public class TruthTableTableModel implements TableModel {
public static final String[] STATENAMES = new String[]{"0", "1", "x"}; public static final String[] STATENAMES = new String[]{"0", "1", "x"};
private final TruthTable truthTable; private final TruthTable truthTable;
private ArrayList<TableModelListener> listeners = new ArrayList<>(); private final ArrayList<TableModelListener> listeners = new ArrayList<>();
/** /**
* Creates a new instance * Creates a new instance

View File

@ -49,6 +49,7 @@ public class SpeedTest {
for (int i = 0; i < LOOPCOUNTER; i++) { for (int i = 0; i < LOOPCOUNTER; i++) {
state = 1 - state; state = 1 - state;
clockValue.setValue(state); clockValue.setValue(state);
model.doStep();
} }
loops++; loops++;
aktTime = System.currentTimeMillis(); aktTime = System.currentTimeMillis();

View File

@ -77,7 +77,7 @@ public final class Keys {
*/ */
public static final Key<Integer> FREQUENCY public static final Key<Integer> FREQUENCY
= new Key.KeyInteger("Frequency", 1) = new Key.KeyInteger("Frequency", 1)
.setComboBoxValues(new Integer[]{1, 2, 5, 10, 20, 50, 100, 200, 500}) .setComboBoxValues(new Integer[]{1, 2, 5, 10, 20, 50, 100, 200, 500, 5000, 50000, 500000})
.setMin(1); .setMin(1);
/** /**
* the bit count of a muxer or decoder * the bit count of a muxer or decoder

View File

@ -20,7 +20,7 @@ public class DataField {
private long[] data; private long[] data;
private final int bits; private final int bits;
private transient ArrayList<DataListener> listeners; private final transient ArrayList<DataListener> listeners = new ArrayList<>();
/** /**
* Creates a new DataField * Creates a new DataField
@ -158,9 +158,9 @@ public class DataField {
* @param l the listener * @param l the listener
*/ */
public void addListener(DataListener l) { public void addListener(DataListener l) {
if (listeners == null) synchronized (listeners) {
listeners = new ArrayList<>(); listeners.add(l);
listeners.add(l); }
} }
/** /**
@ -169,12 +169,9 @@ public class DataField {
* @param l the listener to remove * @param l the listener to remove
*/ */
public void removeListener(DataListener l) { public void removeListener(DataListener l) {
if (listeners == null) synchronized (listeners) {
return; listeners.remove(l);
}
listeners.remove(l);
if (listeners.isEmpty())
listeners = null;
} }
/** /**
@ -183,11 +180,10 @@ public class DataField {
* @param addr the address which value has changed * @param addr the address which value has changed
*/ */
public void fireChanged(int addr) { public void fireChanged(int addr) {
if (listeners == null) synchronized (listeners) {
return; for (DataListener l : listeners)
l.valueChanged(addr);
for (DataListener l : listeners) }
l.valueChanged(addr);
} }
/** /**

View File

@ -6,6 +6,7 @@ import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.shapes.*; import de.neemann.digital.draw.shapes.*;
import de.neemann.digital.draw.shapes.Shape; import de.neemann.digital.draw.shapes.Shape;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@ -261,9 +262,9 @@ public class VisualElement implements Drawable, Moveable, AttributeListener {
* @param pos the position * @param pos the position
* @return true if model is changed * @return true if model is changed
*/ */
public boolean elementClicked(CircuitComponent cc, Point pos) { public boolean elementClicked(CircuitComponent cc, Point pos, Sync modelSync) {
if (interactor != null) if (interactor != null)
return interactor.clicked(cc, pos, ioState, element); return interactor.clicked(cc, pos, ioState, element, modelSync);
else else
return false; return false;
} }
@ -276,9 +277,9 @@ public class VisualElement implements Drawable, Moveable, AttributeListener {
* @param pos the position * @param pos the position
* @return true if model is changed * @return true if model is changed
*/ */
public boolean elementPressed(CircuitComponent cc, Point pos) { public boolean elementPressed(CircuitComponent cc, Point pos, Sync modelSync) {
if (interactor != null) if (interactor != null)
return interactor.pressed(cc, pos, ioState, element); return interactor.pressed(cc, pos, ioState, element, modelSync);
else else
return false; return false;
} }
@ -291,9 +292,9 @@ public class VisualElement implements Drawable, Moveable, AttributeListener {
* @param pos the position * @param pos the position
* @return true if model is changed * @return true if model is changed
*/ */
public boolean elementReleased(CircuitComponent cc, Point pos) { public boolean elementReleased(CircuitComponent cc, Point pos, Sync modelSync) {
if (interactor != null) if (interactor != null)
return interactor.released(cc, pos, ioState, element); return interactor.released(cc, pos, ioState, element, modelSync);
else else
return false; return false;
} }

View File

@ -4,24 +4,28 @@ import de.neemann.digital.core.*;
import de.neemann.digital.core.wiring.Clock; import de.neemann.digital.core.wiring.Clock;
import de.neemann.digital.gui.ErrorStopper; import de.neemann.digital.gui.ErrorStopper;
import de.neemann.digital.gui.GuiModelObserver; import de.neemann.digital.gui.GuiModelObserver;
import de.neemann.digital.gui.StatusInterface;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import javax.swing.*;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* The real time clock which is used to fire the models clocks with realtime signals * The real time clock which is used to fire the models clocks with realtime signals
*
* @author hneemann * @author hneemann
*/ */
public class RealTimeClock implements ModelStateObserver { public class RealTimeClock implements ModelStateObserver {
private final Model model; private final Model model;
private final ScheduledThreadPoolExecutor executor; private final ScheduledThreadPoolExecutor executor;
private final ErrorStopper stopper; private final ErrorStopper stopper;
private final Sync modelSync;
private final StatusInterface status;
private final int frequency; private final int frequency;
private final ObservableValue output; private final ObservableValue output;
private ScheduledFuture<?> timer; private Runner runner;
/** /**
* Creates a new real time clock * Creates a new real time clock
@ -29,11 +33,14 @@ public class RealTimeClock implements ModelStateObserver {
* @param model the model * @param model the model
* @param clock the clock element which is modify * @param clock the clock element which is modify
* @param executor the executor used to schedule the update * @param executor the executor used to schedule the update
* @param status allows sending messages to the status line
*/ */
public RealTimeClock(Model model, Clock clock, ScheduledThreadPoolExecutor executor, ErrorStopper stopper) { public RealTimeClock(Model model, Clock clock, ScheduledThreadPoolExecutor executor, ErrorStopper stopper, Sync modelSync, StatusInterface status) {
this.model = model; this.model = model;
this.executor = executor; this.executor = executor;
this.stopper = stopper; this.stopper = stopper;
this.modelSync = modelSync;
this.status = status;
int f = clock.getFrequency(); int f = clock.getFrequency();
if (f < 1) f = 1; if (f < 1) f = 1;
this.frequency = f; this.frequency = f;
@ -44,27 +51,96 @@ public class RealTimeClock implements ModelStateObserver {
public void handleEvent(ModelEvent event) { public void handleEvent(ModelEvent event) {
switch (event) { switch (event) {
case STARTED: case STARTED:
if (frequency > 50) // if frequency is high it is not necessary to update the GUI at every clock if (frequency > 50) // if frequency is high it is not necessary to update the GUI at every clock change
output.removeObserver(GuiModelObserver.class); modelSync.access(() -> output.removeObserver(GuiModelObserver.class));
int delay = 500 / frequency; int delay = 500000 / frequency;
if (delay < 1) delay = 1; if (delay < 10)
runner = new ThreadRunner();
else
runner = new RealTimeRunner(delay);
break;
case STOPPED:
if (runner != null)
runner.stop();
break;
}
}
timer = executor.scheduleAtFixedRate(() -> SwingUtilities.invokeLater(() -> { interface Runner {
output.setValue(1 - output.getValue()); void stop();
}
/**
* runs with defined rate
*/
private class RealTimeRunner implements Runner {
private final ScheduledFuture<?> timer;
RealTimeRunner(int delay) {
timer = executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try { try {
model.doStep(); modelSync.accessNEx(() -> {
output.setValue(1 - output.getValue());
model.doStep();
});
} catch (NodeException e1) { } catch (NodeException e1) {
stopper.showErrorAndStopModel(Lang.get("msg_clockError"), e1); stopper.showErrorAndStopModel(Lang.get("msg_clockError"), e1);
timer.cancel(false); timer.cancel(false);
} }
}), delay, delay, TimeUnit.MILLISECONDS); }
}, delay, delay, TimeUnit.MICROSECONDS);
}
break; @Override
case STOPPED: public void stop() {
if (timer != null) if (timer != null)
timer.cancel(false); timer.cancel(false);
break; }
}
/**
* runs at fast as possible!
*/
private class ThreadRunner implements Runner {
private final Thread thread;
ThreadRunner() {
thread = new Thread() {
@Override
public void run() {
System.out.println("thread start");
long time = System.currentTimeMillis();
long counter = 0;
try {
while (!interrupted()) {
modelSync.accessNEx(() -> {
output.setValue(1 - output.getValue());
model.doStep();
});
counter++;
}
} catch (NodeException e1) {
stopper.showErrorAndStopModel(Lang.get("msg_clockError"), e1);
}
time = System.currentTimeMillis() - time;
status.setStatus(counter / time / 2 + "kHz");
System.out.println("thread end");
}
};
thread.setDaemon(true);
thread.start();
}
@Override
public void stop() {
thread.interrupt();
} }
} }
} }

View File

@ -11,6 +11,7 @@ import de.neemann.digital.draw.elements.Pins;
import de.neemann.digital.draw.graphics.*; import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.graphics.Polygon; import de.neemann.digital.draw.graphics.Polygon;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*; import java.awt.*;
@ -52,21 +53,25 @@ public class ButtonShape implements Shape {
ioState.getOutput(0).addObserverToValue(guiObserver); ioState.getOutput(0).addObserverToValue(guiObserver);
return new InteractorInterface() { return new InteractorInterface() {
@Override @Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
return false; return false;
} }
@Override @Override
public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
ObservableValue value = ioState.getOutput(0); modelSync.access(() -> {
value.setValue(1); ObservableValue value = ioState.getOutput(0);
value.setValue(1);
});
return true; return true;
} }
@Override @Override
public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
ObservableValue value = ioState.getOutput(0); modelSync.access(() -> {
value.setValue(0); ObservableValue value = ioState.getOutput(0);
value.setValue(0);
});
return true; return true;
} }
}; };

View File

@ -11,6 +11,7 @@ import de.neemann.digital.draw.elements.Pins;
import de.neemann.digital.draw.graphics.*; import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.graphics.Polygon; import de.neemann.digital.draw.graphics.Polygon;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*; import java.awt.*;
@ -18,6 +19,7 @@ import static de.neemann.digital.draw.shapes.OutputShape.SIZE;
/** /**
* The Clock shape * The Clock shape
*
* @author hneemann * @author hneemann
*/ */
public class ClockShape implements Shape { public class ClockShape implements Shape {
@ -49,10 +51,12 @@ public class ClockShape implements Shape {
ioState.getOutput(0).addObserverToValue(guiObserver); // necessary to replot wires also if component itself does not depend on state ioState.getOutput(0).addObserverToValue(guiObserver); // necessary to replot wires also if component itself does not depend on state
return new Interactor() { return new Interactor() {
@Override @Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
ObservableValue value = ioState.getOutput(0); ObservableValue value = ioState.getOutput(0);
if (value.getBits() == 1) { if (value.getBits() == 1) {
value.setValue(1 - value.getValue()); modelSync.access(() -> {
value.setValue(1 - value.getValue());
});
return true; return true;
} }
return false; return false;

View File

@ -16,6 +16,7 @@ import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.OrderMerger; import de.neemann.digital.gui.components.OrderMerger;
import de.neemann.digital.gui.components.data.DataSet; import de.neemann.digital.gui.components.data.DataSet;
import de.neemann.digital.gui.components.data.DataSetObserver; import de.neemann.digital.gui.components.data.DataSetObserver;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*; import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -52,7 +53,7 @@ public class DataShape implements Shape {
public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) { public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) {
return new Interactor() { return new Interactor() {
@Override @Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
dataSet.clear(); dataSet.clear();
return false; return false;
} }

View File

@ -12,6 +12,7 @@ import de.neemann.digital.draw.graphics.*;
import de.neemann.digital.draw.graphics.Polygon; import de.neemann.digital.draw.graphics.Polygon;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.SingleValueDialog; import de.neemann.digital.gui.components.SingleValueDialog;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*; import java.awt.*;
@ -52,17 +53,19 @@ public class InputShape implements Shape {
ioState.getOutput(0).addObserverToValue(guiObserver); ioState.getOutput(0).addObserverToValue(guiObserver);
return new Interactor() { return new Interactor() {
@Override @Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
ObservableValue value = ioState.getOutput(0); ObservableValue value = ioState.getOutput(0);
if (value.getBits() == 1) { if (value.getBits() == 1) {
if (value.supportsHighZ()) { modelSync.access(() -> {
if (value.isHighZ()) value.set(0, false); if (value.supportsHighZ()) {
else if (value.getValue() == 0) value.setValue(1); if (value.isHighZ()) value.set(0, false);
else value.set(0, true); else if (value.getValue() == 0) value.setValue(1);
} else else value.set(0, true);
value.setValue(1 - value.getValue()); } else
value.setValue(1 - value.getValue());
});
} else { } else {
SingleValueDialog.editValue(pos, value); SingleValueDialog.editValue(pos, value, modelSync);
} }
return true; return true;
} }

View File

@ -3,6 +3,7 @@ package de.neemann.digital.draw.shapes;
import de.neemann.digital.core.element.Element; import de.neemann.digital.core.element.Element;
import de.neemann.digital.draw.elements.IOState; import de.neemann.digital.draw.elements.IOState;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*; import java.awt.*;
@ -17,12 +18,12 @@ import java.awt.*;
public abstract class Interactor implements InteractorInterface { public abstract class Interactor implements InteractorInterface {
@Override @Override
public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
return false; return false;
} }
@Override @Override
public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
return false; return false;
} }
} }

View File

@ -3,6 +3,7 @@ package de.neemann.digital.draw.shapes;
import de.neemann.digital.core.element.Element; import de.neemann.digital.core.element.Element;
import de.neemann.digital.draw.elements.IOState; import de.neemann.digital.draw.elements.IOState;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*; import java.awt.*;
@ -23,7 +24,7 @@ public interface InteractorInterface {
* @param ioState the state of the element * @param ioState the state of the element
* @return true if model is changed * @return true if model is changed
*/ */
boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element); boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync);
/** /**
* Called mouse is pressed on running model * Called mouse is pressed on running model
@ -33,7 +34,7 @@ public interface InteractorInterface {
* @param ioState the state of the element * @param ioState the state of the element
* @return true if model is changed * @return true if model is changed
*/ */
boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element); boolean pressed(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync);
/** /**
* Called mouse is released on running model * Called mouse is released on running model
@ -43,5 +44,5 @@ public interface InteractorInterface {
* @param ioState the state of the element * @param ioState the state of the element
* @return true if model is changed * @return true if model is changed
*/ */
boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element); boolean released(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync);
} }

View File

@ -8,6 +8,7 @@ import de.neemann.digital.core.memory.RAMInterface;
import de.neemann.digital.draw.elements.IOState; import de.neemann.digital.draw.elements.IOState;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import de.neemann.digital.gui.components.DataEditor; import de.neemann.digital.gui.components.DataEditor;
import de.neemann.digital.gui.sync.Sync;
import java.awt.*; import java.awt.*;
@ -32,10 +33,10 @@ public class RAMShape extends GenericShape {
public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) { public Interactor applyStateMonitor(IOState ioState, Observer guiObserver) {
return new Interactor() { return new Interactor() {
@Override @Override
public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element) { public boolean clicked(CircuitComponent cc, Point pos, IOState ioState, Element element, Sync modelSync) {
if (element instanceof RAMInterface) { if (element instanceof RAMInterface) {
DataField dataField = ((RAMInterface) element).getMemory(); DataField dataField = ((RAMInterface) element).getMemory();
new DataEditor(cc, dataField).showDialog(); new DataEditor(cc, dataField, modelSync).showDialog();
} }
return false; return false;
} }

View File

@ -5,6 +5,8 @@ import de.neemann.digital.core.ModelStateObserver;
import de.neemann.digital.core.Observer; import de.neemann.digital.core.Observer;
import de.neemann.digital.gui.components.CircuitComponent; import de.neemann.digital.gui.components.CircuitComponent;
import javax.swing.*;
/** /**
* This observer is added to the model if real time timers are started. * This observer is added to the model if real time timers are started.
* This observer paints the CircuitComponent after a step is calculated. * This observer paints the CircuitComponent after a step is calculated.
@ -18,6 +20,7 @@ public class GuiModelObserver implements Observer, ModelStateObserver {
private final CircuitComponent component; private final CircuitComponent component;
private final ModelEvent type; private final ModelEvent type;
private boolean changed = false; private boolean changed = false;
private volatile boolean paintPending;
/** /**
* Creates a new instance. * Creates a new instance.
@ -38,7 +41,13 @@ public class GuiModelObserver implements Observer, ModelStateObserver {
@Override @Override
public void handleEvent(ModelEvent event) { public void handleEvent(ModelEvent event) {
if (changed && event == type) { if (changed && event == type) {
component.paintImmediately(0, 0, component.getWidth(), component.getHeight()); if (!paintPending) {
paintPending = true;
SwingUtilities.invokeLater(() -> {
paintPending = false;
component.paintImmediately(0, 0, component.getWidth(), component.getHeight());
});
}
changed = false; changed = false;
} }
} }

View File

@ -34,6 +34,9 @@ import de.neemann.digital.gui.remote.DigitalHandler;
import de.neemann.digital.gui.remote.RemoteSever; import de.neemann.digital.gui.remote.RemoteSever;
import de.neemann.digital.gui.state.State; import de.neemann.digital.gui.state.State;
import de.neemann.digital.gui.state.StateManager; import de.neemann.digital.gui.state.StateManager;
import de.neemann.digital.gui.sync.LockSync;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import de.neemann.gui.*; import de.neemann.gui.*;
@ -56,7 +59,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
* *
* @author hneemann * @author hneemann
*/ */
public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, ErrorStopper, FileHistory.OpenInterface, DigitalRemoteInterface { public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, ErrorStopper, FileHistory.OpenInterface, DigitalRemoteInterface, StatusInterface {
private static final ArrayList<Key> ATTR_LIST = new ArrayList<>(); private static final ArrayList<Key> ATTR_LIST = new ArrayList<>();
private static boolean experimental; private static boolean experimental;
@ -106,7 +109,9 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
private File filename; private File filename;
private FileHistory fileHistory; private FileHistory fileHistory;
private Sync modelSync;
private Model model; private Model model;
private ModelCreator modelCreator; private ModelCreator modelCreator;
private boolean realtimeClockRunning; private boolean realtimeClockRunning;
@ -615,7 +620,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
public void enter() { public void enter() {
super.enter(); super.enter();
clearModelDescription(); clearModelDescription();
circuitComponent.setModeAndReset(false); circuitComponent.setModeAndReset(false, NoSync.INST);
doStep.setEnabled(false); doStep.setEnabled(false);
runToBreakAction.setEnabled(false); runToBreakAction.setEnabled(false);
} }
@ -662,7 +667,6 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
private boolean createAndStartModel(boolean globalRunClock, ModelEvent updateEvent) { private boolean createAndStartModel(boolean globalRunClock, ModelEvent updateEvent) {
try { try {
circuitComponent.removeHighLighted(); circuitComponent.removeHighLighted();
circuitComponent.setModeAndReset(true);
modelCreator = new ModelCreator(circuitComponent.getCircuit(), library); modelCreator = new ModelCreator(circuitComponent.getCircuit(), library);
@ -674,12 +678,19 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
statusLabel.setText(Lang.get("msg_N_nodes", model.size())); statusLabel.setText(Lang.get("msg_N_nodes", model.size()));
realtimeClockRunning = false; realtimeClockRunning = false;
modelSync = null;
if (globalRunClock) if (globalRunClock)
for (Clock c : model.getClocks()) for (Clock c : model.getClocks())
if (c.getFrequency() > 0) { if (c.getFrequency() > 0) {
model.addObserver(new RealTimeClock(model, c, timerExecuter, this)); if (modelSync == null)
modelSync = new LockSync();
model.addObserver(new RealTimeClock(model, c, timerExecuter, this, modelSync, this));
realtimeClockRunning = true; realtimeClockRunning = true;
} }
if (modelSync == null)
modelSync = NoSync.INST;
circuitComponent.setModeAndReset(true, modelSync);
if (realtimeClockRunning) { if (realtimeClockRunning) {
// if clock is running, enable automatic update of gui // if clock is running, enable automatic update of gui
@ -695,12 +706,12 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
List<String> ordering = circuitComponent.getCircuit().getMeasurementOrdering(); List<String> ordering = circuitComponent.getCircuit().getMeasurementOrdering();
if (settings.get(Keys.SHOW_DATA_TABLE)) if (settings.get(Keys.SHOW_DATA_TABLE))
windowPosManager.register("probe", new ProbeDialog(this, model, updateEvent, ordering)).setVisible(true); windowPosManager.register("probe", new ProbeDialog(this, model, updateEvent, ordering, modelSync)).setVisible(true);
if (settings.get(Keys.SHOW_DATA_GRAPH)) if (settings.get(Keys.SHOW_DATA_GRAPH))
windowPosManager.register("dataset", new DataSetDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering)).setVisible(true); windowPosManager.register("dataset", new DataSetDialog(this, model, updateEvent == ModelEvent.MICROSTEP, ordering, modelSync)).setVisible(true);
if (settings.get(Keys.SHOW_DATA_GRAPH_MICRO)) if (settings.get(Keys.SHOW_DATA_GRAPH_MICRO))
windowPosManager.register("datasetMicro", new DataSetDialog(this, model, true, ordering)).setVisible(true); windowPosManager.register("datasetMicro", new DataSetDialog(this, model, true, ordering, modelSync)).setVisible(true);
int i = 0; int i = 0;
for (ROM rom : model.findNode(ROM.class)) for (ROM rom : model.findNode(ROM.class))
@ -830,6 +841,11 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
return windowPosManager; return windowPosManager;
} }
@Override
public void setStatus(String message) {
SwingUtilities.invokeLater(() -> statusLabel.setText(message));
}
private class FullStepObserver implements Observer { private class FullStepObserver implements Observer {
private final Model model; private final Model model;
@ -840,8 +856,10 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave, E
@Override @Override
public void hasChanged() { public void hasChanged() {
try { try {
model.fireManualChangeEvent(); modelSync.accessNEx(() -> {
model.doStep(); model.fireManualChangeEvent();
model.doStep();
});
circuitComponent.repaint(); circuitComponent.repaint();
} catch (NodeException | RuntimeException e) { } catch (NodeException | RuntimeException e) {
showErrorAndStopModel(Lang.get("msg_errorCalculatingStep"), e); showErrorAndStopModel(Lang.get("msg_errorCalculatingStep"), e);

View File

@ -0,0 +1,15 @@
package de.neemann.digital.gui;
/**
* Interface to acess the status line
*
* @author hneemann
*/
public interface StatusInterface {
/**
* Set message to the status line
*
* @param message the message
*/
void setStatus(String message);
}

View File

@ -13,6 +13,8 @@ import de.neemann.digital.draw.shapes.ShapeFactory;
import de.neemann.digital.gui.LibrarySelector; import de.neemann.digital.gui.LibrarySelector;
import de.neemann.digital.gui.Main; import de.neemann.digital.gui.Main;
import de.neemann.digital.gui.SavedListener; import de.neemann.digital.gui.SavedListener;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import de.neemann.gui.IconCreator; import de.neemann.gui.IconCreator;
import de.neemann.gui.ToolTipAction; import de.neemann.gui.ToolTipAction;
@ -73,6 +75,7 @@ public class CircuitComponent extends JComponent {
private AffineTransform transform = new AffineTransform(); private AffineTransform transform = new AffineTransform();
private Observer manualChangeObserver; private Observer manualChangeObserver;
private Vector lastMousePos; private Vector lastMousePos;
private Sync modelSync;
/** /**
@ -248,13 +251,15 @@ public class CircuitComponent extends JComponent {
* *
* @param runMode true if running, false if editing * @param runMode true if running, false if editing
*/ */
public void setModeAndReset(boolean runMode) { public void setModeAndReset(boolean runMode, Sync modelSync) {
this.modelSync = modelSync;
if (runMode) if (runMode)
mouseRun.activate(); mouseRun.activate();
else else {
mouseNormal.activate(); mouseNormal.activate();
circuit.clearState();
}
requestFocusInWindow(); requestFocusInWindow();
circuit.clearState();
} }
/** /**
@ -372,7 +377,7 @@ public class CircuitComponent extends JComponent {
public void setCircuit(Circuit circuit) { public void setCircuit(Circuit circuit) {
this.circuit = circuit; this.circuit = circuit;
fitCircuit(); fitCircuit();
setModeAndReset(false); setModeAndReset(false, NoSync.INST);
} }
/** /**
@ -982,7 +987,7 @@ public class CircuitComponent extends JComponent {
private interface Actor { private interface Actor {
boolean interact(CircuitComponent cc, Point p); boolean interact(CircuitComponent cc, Point p, Sync modelSync);
} }
private final class MouseControllerRun extends MouseController { private final class MouseControllerRun extends MouseController {
@ -1016,7 +1021,7 @@ public class CircuitComponent extends JComponent {
private void interact(MouseEvent e, Actor actor) { private void interact(MouseEvent e, Actor actor) {
Point p = new Point(e.getX(), e.getY()); Point p = new Point(e.getX(), e.getY());
SwingUtilities.convertPointToScreen(p, CircuitComponent.this); SwingUtilities.convertPointToScreen(p, CircuitComponent.this);
boolean modelHasChanged = actor.interact(CircuitComponent.this, p); boolean modelHasChanged = actor.interact(CircuitComponent.this, p, modelSync);
if (modelHasChanged) { if (modelHasChanged) {
if (manualChangeObserver != null) if (manualChangeObserver != null)
manualChangeObserver.hasChanged(); manualChangeObserver.hasChanged();

View File

@ -3,6 +3,7 @@ package de.neemann.digital.gui.components;
import de.neemann.digital.core.element.ElementAttributes; import de.neemann.digital.core.element.ElementAttributes;
import de.neemann.digital.core.element.Keys; import de.neemann.digital.core.element.Keys;
import de.neemann.digital.core.memory.DataField; import de.neemann.digital.core.memory.DataField;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import javax.swing.*; import javax.swing.*;
@ -32,8 +33,8 @@ public class DataEditor extends JDialog {
* @param parent the parent * @param parent the parent
* @param dataField the data to edit * @param dataField the data to edit
*/ */
public DataEditor(JComponent parent, DataField dataField) { public DataEditor(JComponent parent, DataField dataField, Sync modelSync) {
this(parent, dataField, null); this(parent, dataField, null, modelSync);
} }
/** /**
@ -43,7 +44,7 @@ public class DataEditor extends JDialog {
* @param dataField the data to edit * @param dataField the data to edit
* @param attr uset to get bit sizes * @param attr uset to get bit sizes
*/ */
public DataEditor(JComponent parent, DataField dataField, ElementAttributes attr) { public DataEditor(JComponent parent, DataField dataField, ElementAttributes attr, Sync modelSync) {
super(SwingUtilities.windowForComponent(parent), Lang.get("key_Data"), attr == null ? ModalityType.MODELESS : ModalityType.APPLICATION_MODAL); super(SwingUtilities.windowForComponent(parent), Lang.get("key_Data"), attr == null ? ModalityType.MODELESS : ModalityType.APPLICATION_MODAL);
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);
@ -70,7 +71,7 @@ public class DataEditor extends JDialog {
if (size <= 16) cols = 1; if (size <= 16) cols = 1;
else if (size <= 128) cols = 8; else if (size <= 128) cols = 8;
MyTableModel dm = new MyTableModel(this.dataField, cols); MyTableModel dm = new MyTableModel(this.dataField, cols, modelSync);
JTable table = new JTable(dm); JTable table = new JTable(dm);
table.setDefaultRenderer(MyLong.class, new MyLongRenderer(bits)); table.setDefaultRenderer(MyLong.class, new MyLongRenderer(bits));
getContentPane().add(new JScrollPane(table)); getContentPane().add(new JScrollPane(table));
@ -122,12 +123,14 @@ public class DataEditor extends JDialog {
private final static class MyTableModel implements TableModel, DataField.DataListener { private final static class MyTableModel implements TableModel, DataField.DataListener {
private final DataField dataField; private final DataField dataField;
private final int cols; private final int cols;
private final Sync modelSync;
private final int rows; private final int rows;
private ArrayList<TableModelListener> listener = new ArrayList<>(); private ArrayList<TableModelListener> listener = new ArrayList<>();
private MyTableModel(DataField dataField, int cols) { private MyTableModel(DataField dataField, int cols, Sync modelSync) {
this.dataField = dataField; this.dataField = dataField;
this.cols = cols; this.cols = cols;
this.modelSync = modelSync;
rows = (dataField.size() - 1) / cols + 1; rows = (dataField.size() - 1) / cols + 1;
} }
@ -171,7 +174,9 @@ public class DataEditor extends JDialog {
@Override @Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) { public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
dataField.setData(rowIndex * cols + (columnIndex - 1), ((MyLong) aValue).getValue()); modelSync.access(()->{
dataField.setData(rowIndex * cols + (columnIndex - 1), ((MyLong) aValue).getValue());
});
} }
private void fireEvent(TableModelEvent e) { private void fireEvent(TableModelEvent e) {

View File

@ -8,6 +8,7 @@ import de.neemann.digital.core.memory.DataField;
import de.neemann.digital.core.memory.ROM; import de.neemann.digital.core.memory.ROM;
import de.neemann.digital.gui.components.test.TestData; import de.neemann.digital.gui.components.test.TestData;
import de.neemann.digital.gui.components.test.TestDataEditor; import de.neemann.digital.gui.components.test.TestDataEditor;
import de.neemann.digital.gui.sync.NoSync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import de.neemann.gui.ErrorMessage; import de.neemann.gui.ErrorMessage;
import de.neemann.gui.ToolTipAction; import de.neemann.gui.ToolTipAction;
@ -15,6 +16,7 @@ import de.neemann.gui.language.Bundle;
import de.neemann.gui.language.Language; import de.neemann.gui.language.Language;
import javax.swing.*; import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.io.IOException; import java.io.IOException;
@ -234,7 +236,7 @@ public final class EditorFactory {
panel.add(new ToolTipAction(Lang.get("btn_edit")) { panel.add(new ToolTipAction(Lang.get("btn_edit")) {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
DataEditor de = new DataEditor(panel, data, attr); DataEditor de = new DataEditor(panel, data, attr, NoSync.INST);
if (de.showDialog()) { if (de.showDialog()) {
data = de.getDataField(); data = de.getDataField();
} }
@ -245,6 +247,7 @@ public final class EditorFactory {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
JFileChooser fc = new JFileChooser(); JFileChooser fc = new JFileChooser();
fc.setSelectedFile(attr.getFile(ROM.LAST_DATA_FILE_KEY)); fc.setSelectedFile(attr.getFile(ROM.LAST_DATA_FILE_KEY));
fc.setFileFilter(new FileNameExtensionFilter("hex", "hex"));
if (fc.showOpenDialog(panel) == JFileChooser.APPROVE_OPTION) { if (fc.showOpenDialog(panel) == JFileChooser.APPROVE_OPTION) {
attr.setFile(ROM.LAST_DATA_FILE_KEY, fc.getSelectedFile()); attr.setFile(ROM.LAST_DATA_FILE_KEY, fc.getSelectedFile());
try { try {

View File

@ -4,6 +4,7 @@ import de.neemann.digital.core.Model;
import de.neemann.digital.core.ModelEvent; import de.neemann.digital.core.ModelEvent;
import de.neemann.digital.core.ModelStateObserver; import de.neemann.digital.core.ModelStateObserver;
import de.neemann.digital.core.Signal; import de.neemann.digital.core.Signal;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import javax.swing.*; import javax.swing.*;
@ -27,12 +28,13 @@ public class ProbeDialog extends JDialog implements ModelStateObserver {
/** /**
* Creates a new instance * Creates a new instance
* *
* @param owner the owner * @param owner the owner
* @param model the model to run * @param model the model to run
* @param type the event type which fires a dialog repaint * @param type the event type which fires a dialog repaint
* @param ordering the names list used to order the measurement values * @param ordering the names list used to order the measurement values
* @param modelSync
*/ */
public ProbeDialog(Frame owner, Model model, ModelEvent type, List<String> ordering) { public ProbeDialog(Frame owner, Model model, ModelEvent type, List<String> ordering, Sync modelSync) {
super(owner, Lang.get("win_measures"), false); super(owner, Lang.get("win_measures"), false);
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.type = type; this.type = type;
@ -53,12 +55,12 @@ public class ProbeDialog extends JDialog implements ModelStateObserver {
addWindowListener(new WindowAdapter() { addWindowListener(new WindowAdapter() {
@Override @Override
public void windowOpened(WindowEvent e) { public void windowOpened(WindowEvent e) {
model.addObserver(ProbeDialog.this); modelSync.access(() -> model.addObserver(ProbeDialog.this));
} }
@Override @Override
public void windowClosed(WindowEvent e) { public void windowClosed(WindowEvent e) {
model.removeObserver(ProbeDialog.this); modelSync.access(() -> model.removeObserver(ProbeDialog.this));
} }
}); });
@ -71,7 +73,7 @@ public class ProbeDialog extends JDialog implements ModelStateObserver {
@Override @Override
public void handleEvent(ModelEvent event) { public void handleEvent(ModelEvent event) {
if (event == type || event == ModelEvent.MANUALCHANGE) { if (event == type || event == ModelEvent.MANUALCHANGE) {
tableModel.fireChanged(); SwingUtilities.invokeLater(tableModel::fireChanged);
} }
} }

View File

@ -1,6 +1,7 @@
package de.neemann.digital.gui.components; package de.neemann.digital.gui.components;
import de.neemann.digital.core.ObservableValue; import de.neemann.digital.core.ObservableValue;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import javax.swing.*; import javax.swing.*;
@ -60,16 +61,20 @@ public final class SingleValueDialog extends JDialog {
* @param pos the position to pop up the dialog * @param pos the position to pop up the dialog
* @param value the value to edit * @param value the value to edit
*/ */
public static void editValue(Point pos, ObservableValue value) { public static void editValue(Point pos, ObservableValue value, Sync modelSync) {
String ret = new SingleValueDialog(pos, value.getValueString()).showDialog(); String ret = new SingleValueDialog(pos, value.getValueString()).showDialog();
if (ret != null) { if (ret != null) {
ret = ret.trim(); ret = ret.trim();
if (ret.equals("?") && value.supportsHighZ()) { if (ret.equals("?") && value.supportsHighZ()) {
value.setHighZ(true); modelSync.access(() -> {
value.setHighZ(true);
});
} else { } else {
try { try {
long l = Long.decode(ret); long l = Long.decode(ret);
value.set(l, false); modelSync.access(() -> {
value.set(l, false);
});
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
} }

View File

@ -75,7 +75,7 @@ public class DataSet implements Iterable<DataSample>, Drawable {
* *
* @param sample the DataSample * @param sample the DataSample
*/ */
public void add(DataSample sample) { synchronized void add(DataSample sample) {
while (samples.size() >= maxSize) while (samples.size() >= maxSize)
samples.remove(0); samples.remove(0);
@ -154,7 +154,7 @@ public class DataSet implements Iterable<DataSample>, Drawable {
@Override @Override
public void drawTo(Graphic g, boolean highLight) { synchronized public void drawTo(Graphic g, boolean highLight) {
int x = getTextBorder(); int x = getTextBorder();
int yOffs = SIZE / 2; int yOffs = SIZE / 2;

View File

@ -5,6 +5,7 @@ import de.neemann.digital.core.ModelEvent;
import de.neemann.digital.core.ModelStateObserver; import de.neemann.digital.core.ModelStateObserver;
import de.neemann.digital.core.Signal; import de.neemann.digital.core.Signal;
import de.neemann.digital.gui.components.OrderMerger; import de.neemann.digital.gui.components.OrderMerger;
import de.neemann.digital.gui.sync.Sync;
import de.neemann.digital.lang.Lang; import de.neemann.digital.lang.Lang;
import de.neemann.gui.ErrorMessage; import de.neemann.gui.ErrorMessage;
import de.neemann.gui.ToolTipAction; import de.neemann.gui.ToolTipAction;
@ -29,6 +30,7 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
private static final int MAX_SAMPLE_SIZE = 1000; private static final int MAX_SAMPLE_SIZE = 1000;
private final DataSetComponent dsc; private final DataSetComponent dsc;
private final JScrollPane scrollPane; private final JScrollPane scrollPane;
private final Sync modelSync;
private DataSet dataSet; private DataSet dataSet;
private DataSetObserver dataSetObserver; private DataSetObserver dataSetObserver;
@ -40,8 +42,9 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
* @param microStep true the event type which triggers a new DataSample * @param microStep true the event type which triggers a new DataSample
* @param ordering the ordering of the measurement values * @param ordering the ordering of the measurement values
*/ */
public DataSetDialog(Frame owner, Model model, boolean microStep, List<String> ordering) { public DataSetDialog(Frame owner, Model model, boolean microStep, List<String> ordering, Sync modelSync) {
super(owner, createTitle(microStep), false); super(owner, createTitle(microStep), false);
this.modelSync = modelSync;
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setAlwaysOnTop(true); setAlwaysOnTop(true);
@ -64,12 +67,12 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
addWindowListener(new WindowAdapter() { addWindowListener(new WindowAdapter() {
@Override @Override
public void windowOpened(WindowEvent e) { public void windowOpened(WindowEvent e) {
model.addObserver(DataSetDialog.this); modelSync.access(() -> model.addObserver(DataSetDialog.this));
} }
@Override @Override
public void windowClosed(WindowEvent e) { public void windowClosed(WindowEvent e) {
model.removeObserver(DataSetDialog.this); modelSync.access(() -> model.removeObserver(DataSetDialog.this));
} }
}); });
@ -111,10 +114,14 @@ public class DataSetDialog extends JDialog implements ModelStateObserver {
@Override @Override
public void handleEvent(ModelEvent event) { public void handleEvent(ModelEvent event) {
dataSetObserver.handleEvent(event); modelSync.access(() -> {
dsc.revalidate(); dataSetObserver.handleEvent(event);
dsc.repaint(); });
JScrollBar bar = scrollPane.getHorizontalScrollBar(); SwingUtilities.invokeLater(() -> {
SwingUtilities.invokeLater(() -> bar.setValue(bar.getMaximum())); dsc.revalidate();
dsc.repaint();
JScrollBar bar = scrollPane.getHorizontalScrollBar();
bar.setValue(bar.getMaximum());
});
} }
} }

View File

@ -17,6 +17,10 @@ import javax.swing.*;
import static de.neemann.digital.core.element.PinInfo.input; import static de.neemann.digital.core.element.PinInfo.input;
/** /**
* Graphic card.
* Mostly a RAM module with an additional input bit which selects the visible bank.
* So you can use double buffering.
*
* @author hneemann * @author hneemann
*/ */
public class GraphicCard extends Node implements Element, RAMInterface { public class GraphicCard extends Node implements Element, RAMInterface {

View File

@ -83,7 +83,6 @@ public class ROMListingDialog extends JDialog implements Observer {
list.setSelectedIndex(line); list.setSelectedIndex(line);
}); });
} }
lastAddr = addr; lastAddr = addr;
} }
} }

View File

@ -0,0 +1,41 @@
package de.neemann.digital.gui.sync;
import de.neemann.digital.core.NodeException;
import java.util.concurrent.locks.ReentrantLock;
/**
* Calls the runnables under a reentrant lock.
*
* @author hneemann
*/
public class LockSync implements Sync {
private final ReentrantLock lock;
/**
* Creates a new instance
*/
public LockSync() {
lock = new ReentrantLock();
}
@Override
public void access(Runnable run) {
lock.lock();
try {
run.run();
} finally {
lock.unlock();
}
}
@Override
public void accessNEx(Sync.ModelRun run) throws NodeException {
lock.lock();
try {
run.run();
} finally {
lock.unlock();
}
}
}

View File

@ -0,0 +1,29 @@
package de.neemann.digital.gui.sync;
import de.neemann.digital.core.NodeException;
/**
* Implementation which is used in runtim clock does not run.
* Does no synchronisation at all.
*
* @author hneemann
*/
public final class NoSync implements Sync {
/**
* The single instance
*/
public static final Sync INST = new NoSync();
private NoSync() {
}
@Override
public void access(Runnable run) {
run.run();
}
@Override
public void accessNEx(Sync.ModelRun run) throws NodeException {
run.run();
}
}

View File

@ -0,0 +1,34 @@
package de.neemann.digital.gui.sync;
import de.neemann.digital.core.NodeException;
/**
* Simple sync interface
*
* @author hneemann
*/
public interface Sync {
/**
* Calls the given runnable
*
* @param run the runnable to execute
*/
void access(Runnable run);
/**
* Same as access, but catches an exception
*
* @param run the runnable to execute
* @throws NodeException NodeException
*/
void accessNEx(ModelRun run) throws NodeException;
/**
* Like runnable but throws an exception
*/
interface ModelRun {
void run() throws NodeException;
}
}

View File

@ -0,0 +1,10 @@
/**
* Classes to allow a simple synchronisation of model access.
* The problem is, that every modification of a {@link de.neemann.digital.core.ObservableValue}
* is a model access and needs to be synchronized. Synchronisation is necessary only if
* the runtime clock is running activated. If the runtime clock does not run, all modifications
* on the model are done by the GUI thread.
*
* @author hneemann
*/
package de.neemann.digital.gui.sync;