From da27eee93b1893199dde2b0935cafdb1c6301279 Mon Sep 17 00:00:00 2001 From: Fabian Stemmler Date: Sun, 2 Jun 2019 15:44:42 +0200 Subject: [PATCH] BitDisplay, ManualSwitch now Observable. More Docs added in Timeline. --- .../logic/core/components/BitDisplay.java | 27 ++- .../logic/core/components/ManualSwitch.java | 173 ++++++++------- .../logic/core/timeline/Timeline.java | 46 +++- .../net/mograsim/logic/core/wires/Wire.java | 6 +- .../ui/model/components/GUIManualSwitch.java | 132 ++++++------ .../logic/ui/model/wires/GUIWire.java | 198 +++++++++--------- .../logic/ui/model/wires/WireCrossPoint.java | 96 ++++----- 7 files changed, 381 insertions(+), 297 deletions(-) diff --git a/net.mograsim.logic.core/src/net/mograsim/logic/core/components/BitDisplay.java b/net.mograsim.logic.core/src/net/mograsim/logic/core/components/BitDisplay.java index af2fbc00..2e9b5eda 100644 --- a/net.mograsim.logic.core/src/net/mograsim/logic/core/components/BitDisplay.java +++ b/net.mograsim.logic.core/src/net/mograsim/logic/core/components/BitDisplay.java @@ -1,21 +1,27 @@ package net.mograsim.logic.core.components; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import net.mograsim.logic.core.LogicObservable; +import net.mograsim.logic.core.LogicObserver; import net.mograsim.logic.core.timeline.Timeline; import net.mograsim.logic.core.types.Bit; import net.mograsim.logic.core.types.BitVector; import net.mograsim.logic.core.wires.Wire.ReadEnd; import net.mograsim.logic.core.wires.Wire.ReadWriteEnd; -public class BitDisplay extends BasicComponent +public class BitDisplay extends BasicComponent implements LogicObservable { + private Collection observers; private final ReadEnd in; private BitVector displayedValue; public BitDisplay(Timeline timeline, ReadEnd in) { super(timeline, 1); + observers = new ArrayList<>(); this.in = in; in.registerObserver(this); compute(); @@ -25,6 +31,7 @@ public class BitDisplay extends BasicComponent protected void compute() { displayedValue = in.getValues(); + notifyObservers(); } public BitVector getDisplayedValue() @@ -48,4 +55,22 @@ public class BitDisplay extends BasicComponent { return List.of(); } + + @Override + public void registerObserver(LogicObserver ob) + { + observers.add(ob); + } + + @Override + public void deregisterObserver(LogicObserver ob) + { + observers.remove(ob); + } + + @Override + public void notifyObservers() + { + observers.forEach(ob -> ob.update(this)); + } } diff --git a/net.mograsim.logic.core/src/net/mograsim/logic/core/components/ManualSwitch.java b/net.mograsim.logic.core/src/net/mograsim/logic/core/components/ManualSwitch.java index fc6e9432..b5b230ad 100644 --- a/net.mograsim.logic.core/src/net/mograsim/logic/core/components/ManualSwitch.java +++ b/net.mograsim.logic.core/src/net/mograsim/logic/core/components/ManualSwitch.java @@ -1,74 +1,99 @@ -package net.mograsim.logic.core.components; - -import java.util.List; - -import net.mograsim.logic.core.timeline.Timeline; -import net.mograsim.logic.core.types.Bit; -import net.mograsim.logic.core.wires.Wire.ReadEnd; -import net.mograsim.logic.core.wires.Wire.ReadWriteEnd; - -/** - * This class models a simple on/off (ONE/ZERO) switch for user interaction. - * - * @author Christian Femers - * - */ -public class ManualSwitch extends Component -{ - private ReadWriteEnd output; - private boolean isOn; - - public ManualSwitch(Timeline timeline, ReadWriteEnd output) - { - super(timeline); - if (output.length() != 1) - throw new IllegalArgumentException("Switch output can be only a single wire"); - this.output = output; - } - - public void switchOn() - { - setState(true); - } - - public void switchOff() - { - setState(false); - } - - public void toggle() - { - setState(!isOn); - } - - public void setState(boolean isOn) - { - if (this.isOn == isOn) - return; - this.isOn = isOn; - output.feedSignals(getValue()); - } - - public boolean isOn() - { - return isOn; - } - - public Bit getValue() - { - return isOn ? Bit.ONE : Bit.ZERO; - } - - @Override - public List getAllInputs() - { - return List.of(); - } - - @Override - public List getAllOutputs() - { - return List.of(output); - } - -} +package net.mograsim.logic.core.components; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.mograsim.logic.core.LogicObservable; +import net.mograsim.logic.core.LogicObserver; +import net.mograsim.logic.core.timeline.Timeline; +import net.mograsim.logic.core.types.Bit; +import net.mograsim.logic.core.wires.Wire.ReadEnd; +import net.mograsim.logic.core.wires.Wire.ReadWriteEnd; + +/** + * This class models a simple on/off (ONE/ZERO) switch for user interaction. + * + * @author Christian Femers + * + */ +public class ManualSwitch extends Component implements LogicObservable +{ + private Collection observers; + private ReadWriteEnd output; + private boolean isOn; + + public ManualSwitch(Timeline timeline, ReadWriteEnd output) + { + super(timeline); + observers = new ArrayList<>(); + if (output.length() != 1) + throw new IllegalArgumentException("Switch output can be only a single wire"); + this.output = output; + } + + public void switchOn() + { + setState(true); + } + + public void switchOff() + { + setState(false); + } + + public void toggle() + { + setState(!isOn); + } + + public void setState(boolean isOn) + { + if (this.isOn == isOn) + return; + this.isOn = isOn; + output.feedSignals(getValue()); + notifyObservers(); + } + + public boolean isOn() + { + return isOn; + } + + public Bit getValue() + { + return isOn ? Bit.ONE : Bit.ZERO; + } + + @Override + public List getAllInputs() + { + return List.of(); + } + + @Override + public List getAllOutputs() + { + return List.of(output); + } + + @Override + public void registerObserver(LogicObserver ob) + { + observers.add(ob); + } + + @Override + public void deregisterObserver(LogicObserver ob) + { + observers.remove(ob); + } + + @Override + public void notifyObservers() + { + observers.forEach(ob -> ob.update(this)); + } + +} diff --git a/net.mograsim.logic.core/src/net/mograsim/logic/core/timeline/Timeline.java b/net.mograsim.logic.core/src/net/mograsim/logic/core/timeline/Timeline.java index 80a65f46..d578f2c9 100644 --- a/net.mograsim.logic.core/src/net/mograsim/logic/core/timeline/Timeline.java +++ b/net.mograsim.logic.core/src/net/mograsim/logic/core/timeline/Timeline.java @@ -21,23 +21,33 @@ public class Timeline private final List> eventAddedListener; + public final LongSupplier stepByStepExec = () -> lastTimeUpdated; + public final LongSupplier realTimeExec = () -> System.currentTimeMillis(); + + /** + * Constructs a Timeline object. Per default the time function is set to step by step execution. + * + * @param initCapacity The initial capacity of the event queue. + */ public Timeline(int initCapacity) { events = new PriorityQueue<>(initCapacity); - eventAddedListener = new ArrayList<>(); - time = () -> lastTimeUpdated; + time = stepByStepExec; } /** * @param timestamp exclusive - * @return true if the first event is later than the timestamp + * @return true if the first event in queue is later than the given timestamp */ public BooleanSupplier laterThan(long timestamp) { return () -> timeCmp(events.peek().getTiming(), timestamp) > 0; } + /** + * @return true if there is at least one event enqueued. false otherwise + */ public boolean hasNext() { return !events.isEmpty(); @@ -53,6 +63,9 @@ public class Timeline executeUntil(laterThan(first.getTiming()), -1); } + /** + * Executes all events enqueued in the {@link Timeline}. Use very carefully! Events may generate new events, causing an infinite loop. + */ public void executeAll() { while (hasNext()) @@ -71,7 +84,6 @@ public class Timeline * EXEC_UNTIL_CONDITION if the condition was met * EXEC_UNTIL_EMPTY if events were executed until the {@link Timeline} was empty * @formatter:on - * @author Christian Femers, Fabian Stemmler */ public ExecutionResult executeUntil(BooleanSupplier condition, long stopMillis) { @@ -97,16 +109,31 @@ public class Timeline return hasNext() ? ExecutionResult.EXEC_UNTIL_EMPTY : ExecutionResult.EXEC_UNTIL_CONDITION; } + /** + * Sets the function, which defines the current simulation time at any time. + * + * @param time The return value of calling this function is the current simulation time. + */ public void setTimeFunction(LongSupplier time) { this.time = time; } + /** + * Calculates the current simulation time. + * + * @return The simulation time as defined by the time function. + */ public long getSimulationTime() { return time.getAsLong(); } + /** + * Retrieves the timestamp of the next event. + * + * @return The timestamp of the next enqueued event, if the {@link Timeline} is not empty, -1 otherwise. + */ public long nextEventTime() { if (!hasNext()) @@ -114,17 +141,26 @@ public class Timeline return events.peek().getTiming(); } + /** + * Clears the {@link Timeline} of enqueued events. + */ public void reset() { events.clear(); lastTimeUpdated = 0; } + /** + * Adds a listener, that is called when a {@link TimelineEvent} is added. + */ public void addEventAddedListener(Consumer listener) { eventAddedListener.add(listener); } + /** + * Removes the listener, if possible. It will no longer be called when a {@link TimelineEvent} is added. + */ public void removeEventAddedListener(Consumer listener) { eventAddedListener.remove(listener); @@ -185,7 +221,7 @@ public class Timeline } } - public static int timeCmp(long a, long b) + static int timeCmp(long a, long b) { return Long.signum(a - b); } diff --git a/net.mograsim.logic.core/src/net/mograsim/logic/core/wires/Wire.java b/net.mograsim.logic.core/src/net/mograsim/logic/core/wires/Wire.java index ffbbd328..89fcb80d 100644 --- a/net.mograsim.logic.core/src/net/mograsim/logic/core/wires/Wire.java +++ b/net.mograsim.logic.core/src/net/mograsim/logic/core/wires/Wire.java @@ -186,8 +186,7 @@ public class Wire private void notifyObservers() { - for (ReadEnd o : attached) - o.update(); + attached.forEach(r -> r.update()); } /** @@ -354,8 +353,7 @@ public class Wire @Override public void notifyObservers() { - for (LogicObserver ob : observers) - ob.update(this); + observers.forEach(ob -> ob.update(this)); } } diff --git a/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/components/GUIManualSwitch.java b/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/components/GUIManualSwitch.java index 8ca57ee5..486639e5 100644 --- a/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/components/GUIManualSwitch.java +++ b/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/components/GUIManualSwitch.java @@ -1,67 +1,67 @@ -package net.mograsim.logic.ui.model.components; - -import net.mograsim.logic.ui.model.ViewModel; -import net.mograsim.logic.ui.model.wires.Pin; -import net.haspamelodica.swt.helper.gcs.GeneralGC; -import net.haspamelodica.swt.helper.swtobjectwrappers.Font; -import net.haspamelodica.swt.helper.swtobjectwrappers.Point; -import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle; -import net.mograsim.logic.core.components.ManualSwitch; -import net.mograsim.logic.core.types.BitVectorFormatter; -import net.mograsim.logic.core.wires.Wire.ReadEnd; - -public class GUIManualSwitch extends GUIComponent -{ - private static final double width = 20; - private static final double height = 15; - private static final double fontHeight = 5; - - private final Pin outputPin; - - private ManualSwitch logicSwitch; - private ReadEnd end; - - public GUIManualSwitch(ViewModel model) - { - super(model); - setSize(width, height); - addPin(this.outputPin = new Pin(this, 1, width, height / 2)); - } - - @Override - public void render(GeneralGC gc, Rectangle visibleRegion) - { - double posX = getBounds().x; - double posY = getBounds().y; - - gc.drawRectangle(posX, posY, width, height); - String label = BitVectorFormatter.formatValueAsString(end); - Font oldFont = gc.getFont(); - Font labelFont = new Font(oldFont.getName(), fontHeight, oldFont.getStyle()); - gc.setFont(labelFont); - Point textExtent = gc.textExtent(label); - gc.drawText(label, posX + (width - textExtent.x) / 2, posY + (height - textExtent.y) / 2, true); - gc.setFont(oldFont); - } - - public void setLogicModelBinding(ManualSwitch logicSwitch, ReadEnd end) - { - this.logicSwitch = logicSwitch; - this.end = end; - // TODO when ManualSwitch supports it, add listeners - end.addObserver((i, o) -> callComponentLookChangedListeners()); - } - - @Override - public boolean clicked(double x, double y) - { - if (logicSwitch != null) - logicSwitch.toggle(); - return true; - } - - public Pin getOutputPin() - { - return outputPin; - } +package net.mograsim.logic.ui.model.components; + +import net.mograsim.logic.ui.model.ViewModel; +import net.mograsim.logic.ui.model.wires.Pin; +import net.haspamelodica.swt.helper.gcs.GeneralGC; +import net.haspamelodica.swt.helper.swtobjectwrappers.Font; +import net.haspamelodica.swt.helper.swtobjectwrappers.Point; +import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle; +import net.mograsim.logic.core.components.ManualSwitch; +import net.mograsim.logic.core.types.BitVectorFormatter; +import net.mograsim.logic.core.wires.Wire.ReadEnd; + +public class GUIManualSwitch extends GUIComponent +{ + private static final double width = 20; + private static final double height = 15; + private static final double fontHeight = 5; + + private final Pin outputPin; + + private ManualSwitch logicSwitch; + private ReadEnd end; + + public GUIManualSwitch(ViewModel model) + { + super(model); + setSize(width, height); + addPin(this.outputPin = new Pin(this, 1, width, height / 2)); + } + + @Override + public void render(GeneralGC gc, Rectangle visibleRegion) + { + double posX = getBounds().x; + double posY = getBounds().y; + + gc.drawRectangle(posX, posY, width, height); + String label = BitVectorFormatter.formatValueAsString(end); + Font oldFont = gc.getFont(); + Font labelFont = new Font(oldFont.getName(), fontHeight, oldFont.getStyle()); + gc.setFont(labelFont); + Point textExtent = gc.textExtent(label); + gc.drawText(label, posX + (width - textExtent.x) / 2, posY + (height - textExtent.y) / 2, true); + gc.setFont(oldFont); + } + + public void setLogicModelBinding(ManualSwitch logicSwitch, ReadEnd end) + { + this.logicSwitch = logicSwitch; + this.end = end; + // TODO when ManualSwitch supports it, add listeners + end.registerObserver((i) -> callComponentLookChangedListeners()); + } + + @Override + public boolean clicked(double x, double y) + { + if (logicSwitch != null) + logicSwitch.toggle(); + return true; + } + + public Pin getOutputPin() + { + return outputPin; + } } \ No newline at end of file diff --git a/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/GUIWire.java b/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/GUIWire.java index 736c0ea6..86a545dd 100644 --- a/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/GUIWire.java +++ b/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/GUIWire.java @@ -1,100 +1,100 @@ -package net.mograsim.logic.ui.model.wires; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import net.mograsim.logic.ui.ColorHelper; -import net.mograsim.logic.ui.model.ViewModel; -import net.haspamelodica.swt.helper.gcs.GeneralGC; -import net.haspamelodica.swt.helper.swtobjectwrappers.Point; -import net.mograsim.logic.core.types.BitVectorFormatter; -import net.mograsim.logic.core.wires.Wire.ReadEnd; - -public class GUIWire -{ - private final ViewModel model; - public final int logicWidth; - private Pin pin1; - private Pin pin2; - private double[] path; - - private final List> wireLookChangedListeners; - - private ReadEnd end; - - public GUIWire(ViewModel model, Pin pin1, Pin pin2, Point... path) - { - this.model = model; - this.logicWidth = pin1.logicWidth; - if (pin2.logicWidth != pin1.logicWidth) - throw new IllegalArgumentException("Can't connect pins of different logic width"); - this.path = new double[path.length * 2 + 4]; - for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2) - { - this.path[dstI + 0] = path[srcI].x; - this.path[dstI + 1] = path[srcI].y; - } - - this.pin1 = pin1; - this.pin2 = pin2; - - wireLookChangedListeners = new ArrayList<>(); - - pin1.addPinMovedListener(p -> pin1Moved()); - pin2.addPinMovedListener(p -> pin2Moved()); - pin1Moved(); - pin2Moved(); - - model.wireCreated(this); - } - - private void pin1Moved() - { - Point pos = pin1.getPos(); - this.path[0] = pos.x; - this.path[1] = pos.y; - } - - private void pin2Moved() - { - Point pos = pin2.getPos(); - this.path[this.path.length - 2] = pos.x; - this.path[this.path.length - 1] = pos.y; - } - - public void destroy() - { - model.wireDestroyed(this); - } - - public void render(GeneralGC gc) - { - ColorHelper.executeWithDifferentForeground(gc, BitVectorFormatter.formatAsColor(end), () -> gc.drawPolyline(path)); - } - - public void setLogicModelBinding(ReadEnd end) - { - this.end = end; - end.addObserver((i, o) -> callWireLookChangedListeners()); - } - - public Pin getPin1() - { - return pin1; - } - - public Pin getPin2() - { - return pin2; - } - - // @formatter:off - public void addWireLookChangedListener (Consumer listener) {wireLookChangedListeners.add (listener);} - - public void removeWireLookChangedListener(Consumer listener) {wireLookChangedListeners.remove(listener);} - - private void callWireLookChangedListeners() {wireLookChangedListeners.forEach(l -> l.accept(this));} - // @formatter:on - +package net.mograsim.logic.ui.model.wires; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import net.mograsim.logic.ui.ColorHelper; +import net.mograsim.logic.ui.model.ViewModel; +import net.haspamelodica.swt.helper.gcs.GeneralGC; +import net.haspamelodica.swt.helper.swtobjectwrappers.Point; +import net.mograsim.logic.core.types.BitVectorFormatter; +import net.mograsim.logic.core.wires.Wire.ReadEnd; + +public class GUIWire +{ + private final ViewModel model; + public final int logicWidth; + private Pin pin1; + private Pin pin2; + private double[] path; + + private final List> wireLookChangedListeners; + + private ReadEnd end; + + public GUIWire(ViewModel model, Pin pin1, Pin pin2, Point... path) + { + this.model = model; + this.logicWidth = pin1.logicWidth; + if (pin2.logicWidth != pin1.logicWidth) + throw new IllegalArgumentException("Can't connect pins of different logic width"); + this.path = new double[path.length * 2 + 4]; + for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2) + { + this.path[dstI + 0] = path[srcI].x; + this.path[dstI + 1] = path[srcI].y; + } + + this.pin1 = pin1; + this.pin2 = pin2; + + wireLookChangedListeners = new ArrayList<>(); + + pin1.addPinMovedListener(p -> pin1Moved()); + pin2.addPinMovedListener(p -> pin2Moved()); + pin1Moved(); + pin2Moved(); + + model.wireCreated(this); + } + + private void pin1Moved() + { + Point pos = pin1.getPos(); + this.path[0] = pos.x; + this.path[1] = pos.y; + } + + private void pin2Moved() + { + Point pos = pin2.getPos(); + this.path[this.path.length - 2] = pos.x; + this.path[this.path.length - 1] = pos.y; + } + + public void destroy() + { + model.wireDestroyed(this); + } + + public void render(GeneralGC gc) + { + ColorHelper.executeWithDifferentForeground(gc, BitVectorFormatter.formatAsColor(end), () -> gc.drawPolyline(path)); + } + + public void setLogicModelBinding(ReadEnd end) + { + this.end = end; + end.registerObserver((i) -> callWireLookChangedListeners()); + } + + public Pin getPin1() + { + return pin1; + } + + public Pin getPin2() + { + return pin2; + } + + // @formatter:off + public void addWireLookChangedListener (Consumer listener) {wireLookChangedListeners.add (listener);} + + public void removeWireLookChangedListener(Consumer listener) {wireLookChangedListeners.remove(listener);} + + private void callWireLookChangedListeners() {wireLookChangedListeners.forEach(l -> l.accept(this));} + // @formatter:on + } \ No newline at end of file diff --git a/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/WireCrossPoint.java b/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/WireCrossPoint.java index 0949ffbb..b4aaa98c 100644 --- a/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/WireCrossPoint.java +++ b/net.mograsim.logic.ui/src/net/mograsim/logic/ui/model/wires/WireCrossPoint.java @@ -1,49 +1,49 @@ -package net.mograsim.logic.ui.model.wires; - -import net.mograsim.logic.ui.ColorHelper; -import net.mograsim.logic.ui.model.ViewModel; -import net.mograsim.logic.ui.model.components.GUIComponent; -import net.haspamelodica.swt.helper.gcs.GeneralGC; -import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle; -import net.mograsim.logic.core.types.BitVectorFormatter; -import net.mograsim.logic.core.wires.Wire.ReadEnd; - -public class WireCrossPoint extends GUIComponent -{ - private final Pin pin; - - private ReadEnd end; - private final int logicWidth; - - public WireCrossPoint(ViewModel model, int logicWidth) - { - super(model); - this.logicWidth = logicWidth; - setSize(0, 0); - addPin(this.pin = new Pin(this, logicWidth, 0, 0)); - } - - @Override - public void render(GeneralGC gc, Rectangle visibleRegion) - { - Rectangle bounds = getBounds(); - ColorHelper.executeWithDifferentBackground(gc, BitVectorFormatter.formatAsColor(end), - () -> gc.fillOval(bounds.x - 1, bounds.y - 1, 2, 2)); - } - - public void setLogicModelBinding(ReadEnd end) - { - this.end = end; - end.addObserver((i, o) -> callComponentLookChangedListeners()); - } - - public int getLogicWidth() - { - return logicWidth; - } - - public Pin getPin() - { - return pin; - } +package net.mograsim.logic.ui.model.wires; + +import net.mograsim.logic.ui.ColorHelper; +import net.mograsim.logic.ui.model.ViewModel; +import net.mograsim.logic.ui.model.components.GUIComponent; +import net.haspamelodica.swt.helper.gcs.GeneralGC; +import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle; +import net.mograsim.logic.core.types.BitVectorFormatter; +import net.mograsim.logic.core.wires.Wire.ReadEnd; + +public class WireCrossPoint extends GUIComponent +{ + private final Pin pin; + + private ReadEnd end; + private final int logicWidth; + + public WireCrossPoint(ViewModel model, int logicWidth) + { + super(model); + this.logicWidth = logicWidth; + setSize(0, 0); + addPin(this.pin = new Pin(this, logicWidth, 0, 0)); + } + + @Override + public void render(GeneralGC gc, Rectangle visibleRegion) + { + Rectangle bounds = getBounds(); + ColorHelper.executeWithDifferentBackground(gc, BitVectorFormatter.formatAsColor(end), + () -> gc.fillOval(bounds.x - 1, bounds.y - 1, 2, 2)); + } + + public void setLogicModelBinding(ReadEnd end) + { + this.end = end; + end.registerObserver((i) -> callComponentLookChangedListeners()); + } + + public int getLogicWidth() + { + return logicWidth; + } + + public Pin getPin() + { + return pin; + } } \ No newline at end of file -- 2.17.1