HighLevelStates now support adding/removing listeners
authorDaniel Kirschten <daniel.kirschten@gmx.de>
Wed, 2 Oct 2019 14:04:33 +0000 (16:04 +0200)
committerDaniel Kirschten <daniel.kirschten@gmx.de>
Wed, 2 Oct 2019 14:04:33 +0000 (16:04 +0200)
20 files changed:
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/LogicUICanvas.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/model/components/ModelComponent.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/model/components/atomic/ModelClock.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/model/components/atomic/ModelManualSwitch.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/model/components/atomic/SimpleRectangularHardcodedModelComponent.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/model/wires/ModelWire.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/modeladapter/componentadapters/SimpleRectangularHardcodedModelComponentAdapter.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/HighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/DefaultHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/standard/StandardHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/standard/atomic/AtomicHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/standard/atomic/BitVectorSplittingAtomicHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/standard/atomic/DelegatingAtomicHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/standard/atomic/WireForcingAtomicHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/standard/subcomponent/DelegatingSubcomponentHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/snippets/highlevelstatehandlers/standard/subcomponent/SubcomponentHighLevelStateHandler.java
plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/util/ObservableAtomicReference.java [new file with mode: 0644]
plugins/net.mograsim.machine/src/net/mograsim/machine/Machine.java
plugins/net.mograsim.machine/src/net/mograsim/machine/mi/components/ModelMicroInstructionMemory.java
plugins/net.mograsim.machine/src/net/mograsim/machine/standard/memory/ModelWordAddressableMemory.java

index 34cfb09..fe6c14c 100644 (file)
@@ -1,9 +1,10 @@
 package net.mograsim.logic.model;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 import org.eclipse.swt.SWT;
@@ -89,17 +90,19 @@ public class LogicUICanvas extends ZoomableCanvas
                List<ModelComponent> componentsByItemIndex = new ArrayList<>();
                List<LogicModel> models = new ArrayList<>();
                AtomicBoolean recalculateQueued = new AtomicBoolean();
-               AtomicReference<Consumer<? super ModelComponent>> compAdded = new AtomicReference<>();
-               AtomicReference<Consumer<? super ModelComponent>> compRemoved = new AtomicReference<>();
-               compAdded.set(c -> compsChanged(compAdded.get(), compRemoved.get(), c, models, componentsByItemIndex, componentSelector, model,
-                               recalculateQueued, true));
-               compRemoved.set(c -> compsChanged(compAdded.get(), compRemoved.get(), c, models, componentsByItemIndex, componentSelector, model,
-                               recalculateQueued, false));
-               iterateModelTree(compAdded.get(), compRemoved.get(), model, models, true);
+               @SuppressWarnings("unchecked")
+               Consumer<? super ModelComponent>[] compAdded = new Consumer[1];
+               @SuppressWarnings("unchecked")
+               Consumer<? super ModelComponent>[] compRemoved = new Consumer[1];
+               compAdded[0] = c -> compsChanged(compAdded[0], compRemoved[0], c, models, componentsByItemIndex, componentSelector, model,
+                               recalculateQueued, true);
+               compRemoved[0] = c -> compsChanged(compAdded[0], compRemoved[0], c, models, componentsByItemIndex, componentSelector, model,
+                               recalculateQueued, false);
+               iterateModelTree(compAdded[0], compRemoved[0], model, models, true);
                debugShell.addListener(SWT.Dispose, e -> models.forEach(m ->
                {
-                       m.removeComponentAddedListener(compAdded.get());
-                       m.removeComponentRemovedListener(compRemoved.get());
+                       m.removeComponentAddedListener(compAdded[0]);
+                       m.removeComponentRemovedListener(compRemoved[0]);
                }));
                queueRecalculateComponentSelector(recalculateQueued, componentsByItemIndex, componentSelector, model);
                new Label(debugShell, SWT.NONE).setText("Target state ID: ");
@@ -121,8 +124,12 @@ public class LogicUICanvas extends ZoomableCanvas
                valueText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
                Button send = new Button(debugShell, SWT.PUSH);
                send.setText("Send!");
+               Button addListener = new Button(debugShell, SWT.PUSH);
+               addListener.setText("Add sysout listener");
                Button get = new Button(debugShell, SWT.PUSH);
                get.setText("Get!");
+               Button removeListener = new Button(debugShell, SWT.PUSH);
+               removeListener.setText("Remove sysout listener");
                Text output = new Text(debugShell, SWT.READ_ONLY);
                output.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
                Listener sendAction = e ->
@@ -167,6 +174,53 @@ public class LogicUICanvas extends ZoomableCanvas
                valueText.addListener(SWT.DefaultSelection, sendAction);
                get.addListener(SWT.Selection, getAction);
                stateIDText.addListener(SWT.DefaultSelection, getAction);
+               Map<ModelComponent, Map<String, Consumer<Object>>> sysoutListenersPerHLSPerTarget = new HashMap<>();
+               addListener.addListener(SWT.Selection, e ->
+               {
+                       try
+                       {
+                               int componentIndex = componentSelector.getSelectionIndex();
+                               if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
+                                       throw new RuntimeException("No component selected");
+                               ModelComponent target = componentsByItemIndex.get(componentIndex);
+                               Map<String, Consumer<Object>> sysoutListenersPerHLS = sysoutListenersPerHLSPerTarget.computeIfAbsent(target,
+                                               k -> new HashMap<>());
+                               String stateIDString = stateIDText.getText();
+                               if (sysoutListenersPerHLS.containsKey(stateIDString))
+                                       throw new RuntimeException("Listener already registered");
+                               Consumer<Object> sysoutListener = v -> System.out.println(stateIDString + ": " + v);
+                               target.addHighLevelStateListener(stateIDString, sysoutListener);
+                               sysoutListenersPerHLS.put(stateIDString, sysoutListener);
+                               output.setText("Success!");
+                       }
+                       catch (Exception x)
+                       {
+                               output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
+                       }
+               });
+               removeListener.addListener(SWT.Selection, e ->
+               {
+                       try
+                       {
+                               int componentIndex = componentSelector.getSelectionIndex();
+                               if (componentIndex < 0 || componentIndex >= componentsByItemIndex.size())
+                                       throw new RuntimeException("No component selected");
+                               ModelComponent target = componentsByItemIndex.get(componentIndex);
+                               Map<String, Consumer<Object>> sysoutListenersPerHLS = sysoutListenersPerHLSPerTarget.get(target);
+                               if (sysoutListenersPerHLS == null)
+                                       throw new RuntimeException("Listener not registered");
+                               String stateIDString = stateIDText.getText();
+                               Consumer<Object> sysoutListener = sysoutListenersPerHLS.remove(stateIDString);
+                               if (sysoutListener == null)
+                                       throw new RuntimeException("Listener not registered");
+                               target.removeHighLevelStateListener(stateIDString, sysoutListener);
+                               output.setText("Success!");
+                       }
+                       catch (Exception x)
+                       {
+                               output.setText(x.getClass().getSimpleName() + (x.getMessage() == null ? "" : ": " + x.getMessage()));
+                       }
+               });
                debugShell.open();
                addDisposeListener(e -> debugShell.dispose());
        }
index ded6685..b4d8928 100644 (file)
@@ -215,13 +215,13 @@ public abstract class ModelComponent implements JSONSerializable
         * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
         * 
         * @see #setHighLevelState(String, Object)
-        * @see HighLevelStateHandler#getHighLevelState(String)
+        * @see HighLevelStateHandler#get(String)
         * 
         * @author Daniel Kirschten
         */
        public final Object getHighLevelState(String stateID)
        {
-               return highLevelStateHandler.getHighLevelState(stateID);
+               return highLevelStateHandler.get(stateID);
        }
 
        /**
@@ -229,13 +229,23 @@ public abstract class ModelComponent implements JSONSerializable
         * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
         * 
         * @see #getHighLevelState(String)
-        * @see HighLevelStateHandler#setHighLevelState(String, Object)
+        * @see HighLevelStateHandler#set(String, Object)
         * 
         * @author Daniel Kirschten
         */
        public final void setHighLevelState(String stateID, Object newState)
        {
-               highLevelStateHandler.setHighLevelState(stateID, newState);
+               highLevelStateHandler.set(stateID, newState);
+       }
+
+       public final void addHighLevelStateListener(String stateID, Consumer<Object> stateChanged)
+       {
+               highLevelStateHandler.addListener(stateID, stateChanged);
+       }
+
+       public final void removeHighLevelStateListener(String stateID, Consumer<Object> stateChanged)
+       {
+               highLevelStateHandler.removeListener(stateID, stateChanged);
        }
 
        // "graphical" operations
index 5c54069..3c4eec6 100644 (file)
@@ -1,5 +1,9 @@
 package net.mograsim.logic.model.model.components.atomic;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
 import org.eclipse.swt.graphics.Color;
 
 import com.google.gson.JsonSyntaxException;
@@ -10,6 +14,7 @@ import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
 import net.mograsim.logic.core.LogicObserver;
 import net.mograsim.logic.core.components.CoreClock;
+import net.mograsim.logic.core.types.BitVector;
 import net.mograsim.logic.model.model.LogicModelModifiable;
 import net.mograsim.logic.model.model.components.ModelComponent;
 import net.mograsim.logic.model.model.components.Orientation;
@@ -37,6 +42,8 @@ public class ModelClock extends ModelComponent
        private OrientationCalculator oc;
        private CoreClock clock;
 
+       private final List<Consumer<Object>> hlsListeners;
+
        public ModelClock(LogicModelModifiable model, ModelClockParams params)
        {
                this(model, params, null);
@@ -46,7 +53,6 @@ public class ModelClock extends ModelComponent
        {
                super(model, name, false);
                this.params = params;
-               logicObs = (i) -> model.requestRedraw();
 
                oc = new OrientationCalculator(params.orientation, width, height);
                setSize(oc.width(), oc.height());
@@ -54,16 +60,25 @@ public class ModelClock extends ModelComponent
                this.outputPin = new Pin(model, this, "", 1, PinUsage.OUTPUT, oc.newX(width, height / 2), oc.newY(width, height / 2));
                addPin(outputPin);
 
+               this.hlsListeners = new ArrayList<>();
+
+               logicObs = i ->
+               {
+                       model.requestRedraw();
+                       BitVector v = getOutValues();
+                       hlsListeners.forEach(l -> l.accept(v));
+               };
+
                setHighLevelStateHandler(new HighLevelStateHandler()
                {
                        @Override
-                       public Object getHighLevelState(String stateID)
+                       public Object get(String stateID)
                        {
                                switch (stateID)
                                {
                                case "out":
                                        if (clock != null)
-                                               return clock.getOutValues();
+                                               return getOutValues();
                                        return null;
                                default:
                                        throw new IllegalArgumentException("No high level state with ID " + stateID);
@@ -71,7 +86,7 @@ public class ModelClock extends ModelComponent
                        }
 
                        @Override
-                       public void setHighLevelState(String stateID, Object newState)
+                       public void set(String stateID, Object newState)
                        {
                                switch (stateID)
                                {
@@ -82,6 +97,32 @@ public class ModelClock extends ModelComponent
                                }
                        }
 
+                       @Override
+                       public void addListener(String stateID, Consumer<Object> stateChanged)
+                       {
+                               switch (stateID)
+                               {
+                               case "out":
+                                       hlsListeners.add(stateChanged);
+                                       break;
+                               default:
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                               }
+                       }
+
+                       @Override
+                       public void removeListener(String stateID, java.util.function.Consumer<Object> stateChanged)
+                       {
+                               switch (stateID)
+                               {
+                               case "out":
+                                       hlsListeners.remove(stateChanged);
+                                       break;
+                               default:
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                               }
+                       }
+
                        @Override
                        public String getIDForSerializing(IdentifyParams idParams)
                        {
@@ -131,6 +172,7 @@ public class ModelClock extends ModelComponent
                return clock != null;
        }
 
+       // TODO remove
        public CoreClock getClock()
        {
                return clock;
@@ -158,6 +200,11 @@ public class ModelClock extends ModelComponent
                return params;
        }
 
+       private BitVector getOutValues()
+       {
+               return clock.getOutValues();
+       }
+
        static
        {
                LogicCoreAdapter.addComponentAdapter(new ClockAdapter());
index cf9735a..d4e0437 100644 (file)
@@ -1,5 +1,9 @@
 package net.mograsim.logic.model.model.components.atomic;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
 import org.eclipse.swt.graphics.Color;
 
 import net.haspamelodica.swt.helper.gcs.GeneralGC;
@@ -35,6 +39,8 @@ public class ModelManualSwitch extends ModelComponent
        private final LogicObserver logicObs;
        private CoreManualSwitch manualSwitch;
 
+       private final List<Consumer<Object>> hlsListeners;
+
        public ModelManualSwitch(LogicModelModifiable model, int logicWidth)
        {
                this(model, logicWidth, null);
@@ -44,21 +50,29 @@ public class ModelManualSwitch extends ModelComponent
        {
                super(model, name, false);
                this.logicWidth = logicWidth;
-               logicObs = (i) -> model.requestRedraw();
 
                setSize(width, height);
                addPin(this.outputPin = new Pin(model, this, "", logicWidth, PinUsage.OUTPUT, width, height / 2));
 
+               hlsListeners = new ArrayList<>();
+
+               logicObs = i ->
+               {
+                       model.requestRedraw();
+                       BitVector v = getOutValues();
+                       hlsListeners.forEach(l -> l.accept(v));
+               };
+
                setHighLevelStateHandler(new HighLevelStateHandler()
                {
                        @Override
-                       public Object getHighLevelState(String stateID)
+                       public Object get(String stateID)
                        {
                                switch (stateID)
                                {
                                case "out":
                                        if (manualSwitch != null)
-                                               return manualSwitch.getValues();
+                                               return getOutValues();
                                        return null;
                                default:
                                        throw new IllegalArgumentException("No high level state with ID " + stateID);
@@ -66,7 +80,7 @@ public class ModelManualSwitch extends ModelComponent
                        }
 
                        @Override
-                       public void setHighLevelState(String stateID, Object newState)
+                       public void set(String stateID, Object newState)
                        {
                                switch (stateID)
                                {
@@ -79,6 +93,32 @@ public class ModelManualSwitch extends ModelComponent
                                }
                        }
 
+                       @Override
+                       public void addListener(String stateID, Consumer<Object> stateChanged)
+                       {
+                               switch (stateID)
+                               {
+                               case "out":
+                                       hlsListeners.add(stateChanged);
+                                       break;
+                               default:
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                               }
+                       }
+
+                       @Override
+                       public void removeListener(String stateID, java.util.function.Consumer<Object> stateChanged)
+                       {
+                               switch (stateID)
+                               {
+                               case "out":
+                                       hlsListeners.remove(stateChanged);
+                                       break;
+                               default:
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                               }
+                       }
+
                        @Override
                        public String getIDForSerializing(IdentifyParams idParams)
                        {
@@ -102,7 +142,7 @@ public class ModelManualSwitch extends ModelComponent
                if (foreground != null)
                        gc.setForeground(foreground);
                gc.drawRectangle(getBounds());
-               String label = BitVectorFormatter.formatAsString(manualSwitch == null ? null : manualSwitch.getValues());
+               String label = BitVectorFormatter.formatAsString(manualSwitch == null ? null : getOutValues());
                Font oldFont = gc.getFont();
                Font labelFont = new Font(oldFont.getName(), fontHeight, oldFont.getStyle());
                gc.setFont(labelFont);
@@ -120,7 +160,7 @@ public class ModelManualSwitch extends ModelComponent
                        gc.drawLine(x, y + heightMiniButtons, x + width, y + heightMiniButtons);
                        Color c = gc.getBackground();
                        gc.setBackground(gc.getForeground());
-                       BitVector bv = manualSwitch.getValues();
+                       BitVector bv = getOutValues();
                        double part = width / bv.length();
                        for (int i = 0; i < bv.length(); i++)
                        {
@@ -160,7 +200,7 @@ public class ModelManualSwitch extends ModelComponent
                        if (heightMiniButtons > 0 && y - getPosY() < heightMiniButtons)
                        {
                                int part = (int) ((x - getPosX()) * logicWidth / width);
-                               manualSwitch.setState(manualSwitch.getValues().withBitChanged(part, Bit::not));
+                               manualSwitch.setState(getOutValues().withBitChanged(part, Bit::not));
                        } else
                        {
                                manualSwitch.toggle();
@@ -191,6 +231,11 @@ public class ModelManualSwitch extends ModelComponent
                return logicWidth;
        }
 
+       private BitVector getOutValues()
+       {
+               return manualSwitch.getValues();
+       }
+
        static
        {
                LogicCoreAdapter.addComponentAdapter(new ManualSwitchAdapter());
index 8971285..427a179 100644 (file)
@@ -1,7 +1,10 @@
 package net.mograsim.logic.model.model.components.atomic;
 
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 
 import net.haspamelodica.swt.helper.gcs.GeneralGC;
 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
@@ -20,6 +23,7 @@ import net.mograsim.logic.model.snippets.symbolrenderers.CenteredTextSymbolRende
 import net.mograsim.logic.model.snippets.symbolrenderers.PinNamesSymbolRenderer;
 import net.mograsim.logic.model.snippets.symbolrenderers.PinNamesSymbolRenderer.PinNamesParams;
 import net.mograsim.logic.model.snippets.symbolrenderers.PinNamesSymbolRenderer.PinNamesParams.Position;
+import net.mograsim.logic.model.util.ObservableAtomicReference;
 
 public abstract class SimpleRectangularHardcodedModelComponent extends ModelComponent
 {
@@ -33,9 +37,11 @@ public abstract class SimpleRectangularHardcodedModelComponent extends ModelComp
        private final CenteredTextSymbolRenderer centerTextRenderer;
        private final PinNamesSymbolRenderer pinNamesRenderer;
 
-       private AtomicReference<Object> state;
+       private ObservableAtomicReference<Object> state;
        private Runnable recalculate;
 
+       private final Map<String, Map<Consumer<Object>, Consumer<ObservableAtomicReference<Object>>>> stateObsPerHLSListenerPerStateID;
+
        // creation and destruction
 
        public SimpleRectangularHardcodedModelComponent(LogicModelModifiable model, String id, String name, String centerText)
@@ -57,31 +63,47 @@ public abstract class SimpleRectangularHardcodedModelComponent extends ModelComp
                pinNamesParams.pinLabelMargin = pinNamesMargin;
                this.pinNamesRenderer = new PinNamesSymbolRenderer(this, pinNamesParams);
                addPinRemovedListener(this::pinRemoved);
+
+               this.stateObsPerHLSListenerPerStateID = new HashMap<>();
+
                setHighLevelStateHandler(new HighLevelStateHandler()
                {
+
                        @Override
-                       public String getIDForSerializing(IdentifyParams idParams)
+                       public Object get(String stateID)
                        {
-                               return null;// we don't need to serialize this; it's implicit since we are a SimpleRectangularHardcodedModelComponent
+                               return getHighLevelState(state.get(), stateID);
                        }
 
                        @Override
-                       public Object getParamsForSerializing(IdentifyParams idParams)
+                       public void set(String stateID, Object newState)
                        {
-                               return null;
+                               state.updateAndGet(s -> SimpleRectangularHardcodedModelComponent.this.setHighLevelState(s, stateID, newState));
+                               recalculate.run();
                        }
 
                        @Override
-                       public Object getHighLevelState(String stateID)
+                       public void addListener(String stateID, Consumer<Object> stateChanged)
                        {
-                               return SimpleRectangularHardcodedModelComponent.this.getHighLevelState(state.get(), stateID);
+                               addHighLevelStateListener(state.get(), stateID, stateChanged);
                        }
 
                        @Override
-                       public void setHighLevelState(String stateID, Object newState)
+                       public void removeListener(String stateID, Consumer<Object> stateChanged)
                        {
-                               state.updateAndGet(s -> SimpleRectangularHardcodedModelComponent.this.setHighLevelState(s, stateID, newState));
-                               recalculate.run();
+                               removeHighLevelStateListener(state.get(), stateID, stateChanged);
+                       }
+
+                       @Override
+                       public String getIDForSerializing(IdentifyParams idParams)
+                       {
+                               return null;// we don't need to serialize this; it's implicit since we are a SimpleRectangularHardcodedModelComponent
+                       }
+
+                       @Override
+                       public Object getParamsForSerializing(IdentifyParams idParams)
+                       {
+                               return null;
                        }
                });
 
@@ -116,13 +138,36 @@ public abstract class SimpleRectangularHardcodedModelComponent extends ModelComp
                throw new IllegalArgumentException("No high level state with ID " + stateID);
        }
 
+       protected void addHighLevelStateListener(Object state, String stateID, Consumer<Object> stateChanged)
+       {
+               AtomicReference<Object> lastHLSRef = new AtomicReference<>(getHighLevelState(state, stateID));
+               Consumer<ObservableAtomicReference<Object>> refObs = r ->
+               {
+                       Object newHLS = getHighLevelState(stateID);
+                       if (!Objects.equals(lastHLSRef.getAndSet(newHLS), newHLS))
+                               stateChanged.accept(newHLS);
+               };
+               stateObsPerHLSListenerPerStateID.computeIfAbsent(stateID, s -> new HashMap<>()).put(stateChanged, refObs);
+               this.state.addObserver(refObs);
+       }
+
+       protected void removeHighLevelStateListener(Object state, String stateID, Consumer<Object> stateChanged)
+       {
+               getHighLevelState(state, stateID);// if this throws, we know there is no HLS with this name
+               var stateObsPerHLSListener = stateObsPerHLSListenerPerStateID.get(stateID);
+               if (stateObsPerHLSListener == null)
+                       return;
+               Consumer<ObservableAtomicReference<Object>> refObs = stateObsPerHLSListener.remove(stateChanged);
+               this.state.removeObserver(refObs);
+       }
+
        // logic
 
        public abstract Object recalculate(Object lastState, Map<String, ReadEnd> readEnds, Map<String, ReadWriteEnd> readWriteEnds);
 
        // core model binding
 
-       public void setCoreModelBindingAndResetState(AtomicReference<Object> state, Runnable recalculate)
+       public void setCoreModelBindingAndResetState(ObservableAtomicReference<Object> state, Runnable recalculate)
        {
                this.state = state;
                this.recalculate = recalculate;
index 1f44f57..8d3cbde 100644 (file)
@@ -513,6 +513,28 @@ public class ModelWire
                return end.getValues();
        }
 
+       /**
+        * Registers the given {@link LogicObserver} for the {@link ReadEnd} this {@link ModelWire} is bound to.
+        * 
+        * @see ReadEnd#registerObserver(LogicObserver)
+        * @author Daniel Kirschten
+        */
+       public void addObserver(LogicObserver obs)
+       {
+               end.registerObserver(obs);
+       }
+
+       /**
+        * Deregisters the given {@link LogicObserver} for the {@link ReadEnd} this {@link ModelWire} is bound to.
+        * 
+        * @see ReadEnd#deregisterObserver(LogicObserver)
+        * @author Daniel Kirschten
+        */
+       public void removeObserver(LogicObserver obs)
+       {
+               end.deregisterObserver(obs);
+       }
+
        // listeners
 
        // @formatter:off
index dbebc0f..e4bd59d 100644 (file)
@@ -2,7 +2,6 @@ package net.mograsim.logic.model.modeladapter.componentadapters;
 
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
 
 import net.mograsim.logic.core.LogicObserver;
 import net.mograsim.logic.core.timeline.Timeline;
@@ -13,6 +12,7 @@ import net.mograsim.logic.model.model.components.atomic.SimpleRectangularHardcod
 import net.mograsim.logic.model.model.wires.Pin;
 import net.mograsim.logic.model.model.wires.PinUsage;
 import net.mograsim.logic.model.modeladapter.CoreModelParameters;
+import net.mograsim.logic.model.util.ObservableAtomicReference;
 
 public class SimpleRectangularHardcodedModelComponentAdapter implements ComponentAdapter<SimpleRectangularHardcodedModelComponent>
 {
@@ -29,7 +29,7 @@ public class SimpleRectangularHardcodedModelComponentAdapter implements Componen
                Map<String, ReadEnd> readEnds = new HashMap<>();
                Map<String, ReadWriteEnd> readWriteEnds = new HashMap<>();
 
-               AtomicReference<Object> state = new AtomicReference<>();
+               ObservableAtomicReference<Object> state = new ObservableAtomicReference<>();
 
                Runnable recalculate = () -> state.updateAndGet(s -> modelComponent.recalculate(s, readEnds, readWriteEnds));
                LogicObserver logicObs = c -> timeline.addEvent(e -> recalculate.run(), params.gateProcessTime);
index 622df00..7859092 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.logic.model.snippets;
 
+import java.util.function.Consumer;
+
 import net.mograsim.logic.model.model.components.ModelComponent;
 import net.mograsim.logic.model.serializing.JSONSerializable;
 
@@ -20,22 +22,25 @@ public interface HighLevelStateHandler extends JSONSerializable
         * Gets the current value of the given high-level state. <br>
         * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
         * 
-        * @see #setHighLevelState(String, Object)
+        * @see #set(String, Object)
         * @see ModelComponent#getHighLevelState(String)
         * 
         * @author Daniel Kirschten
         */
-       public Object getHighLevelState(String stateID);
+       public Object get(String stateID);
 
        /**
         * Sets the given high-level state to the given value. <br>
         * See {@link HighLevelStateHandler} for an explanation of high-level state IDs.
         * 
-        * @see #getHighLevelState(String)
+        * @see #get(String)
         * @see ModelComponent#setHighLevelState(String, Object)
         * 
         * @author Daniel Kirschten
         */
-       public void setHighLevelState(String stateID, Object newState);
+       public void set(String stateID, Object newState);
+
+       public void addListener(String stateID, Consumer<Object> stateChanged);
 
+       public void removeListener(String stateID, Consumer<Object> stateChanged);
 }
\ No newline at end of file
index 7fd1fc9..34b27a9 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.logic.model.snippets.highlevelstatehandlers;
 
+import java.util.function.Consumer;
+
 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
 import net.mograsim.logic.model.serializing.IdentifyParams;
 import net.mograsim.logic.model.snippets.HighLevelStateHandler;
@@ -25,13 +27,25 @@ public class DefaultHighLevelStateHandler implements HighLevelStateHandler
        }
 
        @Override
-       public Object getHighLevelState(String stateID)
+       public Object get(String stateID)
+       {
+               throw new IllegalArgumentException("No high level state with ID " + stateID);
+       }
+
+       @Override
+       public void set(String stateID, Object newState)
+       {
+               throw new IllegalArgumentException("No high level state with ID " + stateID);
+       }
+
+       @Override
+       public void addListener(String stateID, Consumer<Object> stateChanged)
        {
                throw new IllegalArgumentException("No high level state with ID " + stateID);
        }
 
        @Override
-       public void setHighLevelState(String stateID, Object newState)
+       public void removeListener(String stateID, Consumer<Object> stateChanged)
        {
                throw new IllegalArgumentException("No high level state with ID " + stateID);
        }
index 58e8e64..ddb52ae 100644 (file)
@@ -6,6 +6,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TreeMap;
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
@@ -129,7 +130,7 @@ public class StandardHighLevelStateHandler implements HighLevelStateHandler
        }
 
        @Override
-       public Object getHighLevelState(String stateID)
+       public Object get(String stateID)
        {
                int indexOfDot = stateID.indexOf('.');
                if (indexOfDot == -1)
@@ -147,7 +148,7 @@ public class StandardHighLevelStateHandler implements HighLevelStateHandler
        }
 
        @Override
-       public void setHighLevelState(String stateID, Object newState)
+       public void set(String stateID, Object newState)
        {
                int indexOfDot = stateID.indexOf('.');
                if (indexOfDot == -1)
@@ -167,6 +168,48 @@ public class StandardHighLevelStateHandler implements HighLevelStateHandler
                }
        }
 
+       @Override
+       public void addListener(String stateID, Consumer<Object> stateChanged)
+       {
+               int indexOfDot = stateID.indexOf('.');
+               if (indexOfDot == -1)
+               {
+                       AtomicHighLevelStateHandler handler = atomicHighLevelStateHandlers.get(stateID);
+                       if (handler != null)
+                               handler.addListener(stateChanged);
+                       else
+                               throw new IllegalArgumentException("No high level state with ID " + stateID);
+               } else
+               {
+                       SubcomponentHighLevelStateHandler handler = subcomponentHighLevelStateHandlers.get(stateID.substring(0, indexOfDot));
+                       if (handler != null)
+                               handler.addListener(stateID.substring(indexOfDot + 1), stateChanged);
+                       else
+                               throw new IllegalArgumentException("No high level state with ID " + stateID);
+               }
+       }
+
+       @Override
+       public void removeListener(String stateID, Consumer<Object> stateChanged)
+       {
+               int indexOfDot = stateID.indexOf('.');
+               if (indexOfDot == -1)
+               {
+                       AtomicHighLevelStateHandler handler = atomicHighLevelStateHandlers.get(stateID);
+                       if (handler != null)
+                               handler.removeListener(stateChanged);
+                       else
+                               throw new IllegalArgumentException("No high level state with ID " + stateID);
+               } else
+               {
+                       SubcomponentHighLevelStateHandler handler = subcomponentHighLevelStateHandlers.get(stateID.substring(0, indexOfDot));
+                       if (handler != null)
+                               handler.removeListener(stateID.substring(indexOfDot + 1), stateChanged);
+                       else
+                               throw new IllegalArgumentException("No high level state with ID " + stateID);
+               }
+       }
+
        @Override
        public String getIDForSerializing(IdentifyParams idParams)
        {
index 97ace09..cbcd95e 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.logic.model.snippets.highlevelstatehandlers.standard.atomic;
 
+import java.util.function.Consumer;
+
 import com.google.gson.JsonElement;
 
 import net.mograsim.logic.model.serializing.JSONSerializable;
@@ -28,4 +30,8 @@ public interface AtomicHighLevelStateHandler extends JSONSerializable
                public String id;
                public JsonElement params;
        }
+
+       public void addListener(Consumer<Object> stateChanged);
+
+       public void removeListener(Consumer<Object> stateChanged);
 }
\ No newline at end of file
index 7ae2912..4289808 100644 (file)
@@ -2,7 +2,10 @@ package net.mograsim.logic.model.snippets.highlevelstatehandlers.standard.atomic
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
 
 import net.mograsim.logic.core.types.Bit;
 import net.mograsim.logic.core.types.BitVector;
@@ -20,6 +23,8 @@ public class BitVectorSplittingAtomicHighLevelStateHandler implements AtomicHigh
        private final List<Integer> vectorPartLengthesUnmodifiable;
        private int length;
 
+       private final Map<Consumer<Object>, Consumer<Object>> targetListeners;
+
        public BitVectorSplittingAtomicHighLevelStateHandler(SubmodelComponent component)
        {
                this(component, null);
@@ -33,6 +38,9 @@ public class BitVectorSplittingAtomicHighLevelStateHandler implements AtomicHigh
                this.vectorPartTargetsUnmodifiable = Collections.unmodifiableList(vectorPartTargets);
                this.vectorPartLengthes = new ArrayList<>();
                this.vectorPartLengthesUnmodifiable = Collections.unmodifiableList(vectorPartLengthes);
+
+               this.targetListeners = new HashMap<>();
+
                if (params != null)
                        setVectorParts(params.vectorPartTargets, params.vectorPartLengthes);
        }
@@ -111,6 +119,32 @@ public class BitVectorSplittingAtomicHighLevelStateHandler implements AtomicHigh
                }
        }
 
+       @Override
+       public void addListener(Consumer<Object> stateChanged)
+       {
+               if (targetListeners.get(stateChanged) != null)
+                       // this listener is/was already registered
+                       return;
+
+               Consumer<Object> targetListener = o -> stateChanged.accept(getHighLevelState());
+               targetListeners.put(stateChanged, targetListener);
+
+               for (String target : vectorPartTargets)
+                       component.addHighLevelStateListener(target, targetListener);
+       }
+
+       @Override
+       public void removeListener(Consumer<Object> stateChanged)
+       {
+               Consumer<Object> targetListener = targetListeners.get(stateChanged);
+               if (targetListener == null)
+                       // this listener is/was not registered
+                       return;
+
+               for (String target : vectorPartTargets)
+                       component.removeHighLevelStateListener(target, targetListener);
+       }
+
        @Override
        public String getIDForSerializing(IdentifyParams idParams)
        {
index cdc7e4a..98890fe 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.logic.model.snippets.highlevelstatehandlers.standard.atomic;
 
+import java.util.function.Consumer;
+
 import net.mograsim.logic.model.model.components.ModelComponent;
 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
 import net.mograsim.logic.model.serializing.IdentifyParams;
@@ -75,17 +77,35 @@ public class DelegatingAtomicHighLevelStateHandler implements AtomicHighLevelSta
        @Override
        public Object getHighLevelState()
        {
-               if (delegateTarget == null)
-                       throw new IllegalStateException("Delegating to a component that was destroyed");
+               checkTarget();
                return delegateTarget.getHighLevelState(subStateID);
        }
 
        @Override
        public void setHighLevelState(Object newState)
+       {
+               checkTarget();
+               delegateTarget.setHighLevelState(subStateID, newState);
+       }
+
+       @Override
+       public void addListener(Consumer<Object> stateChanged)
+       {
+               checkTarget();
+               delegateTarget.addHighLevelStateListener(subStateID, stateChanged);
+       }
+
+       @Override
+       public void removeListener(Consumer<Object> stateChanged)
+       {
+               checkTarget();
+               delegateTarget.removeHighLevelStateListener(subStateID, stateChanged);
+       }
+
+       private void checkTarget()
        {
                if (delegateTarget == null)
                        throw new IllegalStateException("Delegating to a component that was destroyed");
-               delegateTarget.setHighLevelState(subStateID, newState);
        }
 
        @Override
@@ -97,8 +117,7 @@ public class DelegatingAtomicHighLevelStateHandler implements AtomicHighLevelSta
        @Override
        public DelegatingAtomicHighLevelStateHandlerParams getParamsForSerializing(IdentifyParams idParams)
        {
-               if (delegateTarget == null)
-                       throw new IllegalStateException("Delegating to a component that was destroyed");
+               checkTarget();
                DelegatingAtomicHighLevelStateHandlerParams params = new DelegatingAtomicHighLevelStateHandlerParams();
                params.delegateTarget = delegateTarget == parentComponent ? null : delegateTarget.getName();
                params.subStateID = subStateID;
index aa509c9..4f7cd45 100644 (file)
@@ -2,11 +2,16 @@ package net.mograsim.logic.model.snippets.highlevelstatehandlers.standard.atomic
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import net.mograsim.logic.core.LogicObserver;
 import net.mograsim.logic.core.types.Bit;
 import net.mograsim.logic.core.types.BitVector;
 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
@@ -24,6 +29,8 @@ public class WireForcingAtomicHighLevelStateHandler implements AtomicHighLevelSt
        private final List<ModelWire> wiresToForceInverted;
        private final List<ModelWire> wiresToForceInvertedUnmodifiable;
 
+       private final Map<Consumer<Object>, LogicObserver> wireObsPerListener;
+
        public WireForcingAtomicHighLevelStateHandler(SubmodelComponent component)
        {
                this(component, null);
@@ -36,6 +43,9 @@ public class WireForcingAtomicHighLevelStateHandler implements AtomicHighLevelSt
                this.wiresToForceUnmodifiable = Collections.unmodifiableList(wiresToForce);
                this.wiresToForceInverted = new ArrayList<>();
                this.wiresToForceInvertedUnmodifiable = Collections.unmodifiableList(wiresToForceInverted);
+
+               this.wireObsPerListener = new HashMap<>();
+
                if (params != null)
                {
                        Map<String, ModelWire> wiresByName = component.submodel.getWiresByName();
@@ -133,6 +143,37 @@ public class WireForcingAtomicHighLevelStateHandler implements AtomicHighLevelSt
                                wire.forceWireValues(vector);
        }
 
+       @Override
+       public void addListener(Consumer<Object> stateChanged)
+       {
+               if (wireObsPerListener.containsKey(stateChanged))
+                       return;
+               AtomicReference<Object> lastStateRef = new AtomicReference<>(getHighLevelState());
+               LogicObserver obs = w ->
+               {
+                       Object newState = getHighLevelState();
+                       if (!Objects.equals(lastStateRef.getAndSet(newState), newState))
+                               stateChanged.accept(newState);
+               };
+               wireObsPerListener.put(stateChanged, obs);
+               for (ModelWire w : wiresToForce)
+                       w.addObserver(obs);
+               for (ModelWire w : wiresToForceInverted)
+                       w.addObserver(obs);
+       }
+
+       @Override
+       public void removeListener(Consumer<Object> stateChanged)
+       {
+               LogicObserver obs = wireObsPerListener.remove(stateChanged);
+               if (obs == null)
+                       return;
+               for (ModelWire w : wiresToForce)
+                       w.removeObserver(obs);
+               for (ModelWire w : wiresToForceInverted)
+                       w.removeObserver(obs);
+       }
+
        @Override
        public String getIDForSerializing(IdentifyParams idParams)
        {
index 104cb14..6557787 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.logic.model.snippets.highlevelstatehandlers.standard.subcomponent;
 
+import java.util.function.Consumer;
+
 import net.mograsim.logic.model.model.components.ModelComponent;
 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
 import net.mograsim.logic.model.serializing.IdentifyParams;
@@ -76,17 +78,35 @@ public class DelegatingSubcomponentHighLevelStateHandler implements Subcomponent
        @Override
        public Object getHighLevelState(String subStateID)
        {
-               if (delegateTarget == null)
-                       throw new IllegalStateException("Delegating to a component that was destroyed");
+               checkTarget();
                return delegateTarget.getHighLevelState(getDelegateTargetHighLevelStateID(subStateID));
        }
 
        @Override
        public void setHighLevelState(String subStateID, Object newState)
+       {
+               checkTarget();
+               delegateTarget.setHighLevelState(getDelegateTargetHighLevelStateID(subStateID), newState);
+       }
+
+       @Override
+       public void addListener(String subStateID, Consumer<Object> stateChanged)
+       {
+               checkTarget();
+               delegateTarget.addHighLevelStateListener(getDelegateTargetHighLevelStateID(subStateID), stateChanged);
+       }
+
+       @Override
+       public void removeListener(String subStateID, Consumer<Object> stateChanged)
+       {
+               checkTarget();
+               delegateTarget.removeHighLevelStateListener(getDelegateTargetHighLevelStateID(subStateID), stateChanged);
+       }
+
+       private void checkTarget()
        {
                if (delegateTarget == null)
                        throw new IllegalStateException("Delegating to a component that was destroyed");
-               delegateTarget.setHighLevelState(getDelegateTargetHighLevelStateID(subStateID), newState);
        }
 
        private String getDelegateTargetHighLevelStateID(String subStateID)
@@ -103,8 +123,7 @@ public class DelegatingSubcomponentHighLevelStateHandler implements Subcomponent
        @Override
        public DelegatingSubcomponentHighLevelStateHandlerParams getParamsForSerializing(IdentifyParams idParams)
        {
-               if (delegateTarget == null)
-                       throw new IllegalStateException("Delegating to a component that was destroyed");
+               checkTarget();
                DelegatingSubcomponentHighLevelStateHandlerParams params = new DelegatingSubcomponentHighLevelStateHandlerParams();
                params.delegateTarget = delegateTarget == parentComponent ? null : delegateTarget.getName();
                params.prefix = prefix;
index 4c82bdc..fdc633c 100644 (file)
@@ -1,5 +1,7 @@
 package net.mograsim.logic.model.snippets.highlevelstatehandlers.standard.subcomponent;
 
+import java.util.function.Consumer;
+
 import com.google.gson.JsonElement;
 
 import net.mograsim.logic.model.serializing.JSONSerializable;
@@ -28,4 +30,8 @@ public interface SubcomponentHighLevelStateHandler extends JSONSerializable
                public String id;
                public JsonElement params;
        }
+
+       public void addListener(String subStateID, Consumer<Object> stateChanged);
+
+       public void removeListener(String subStateID, Consumer<Object> stateChanged);
 }
\ No newline at end of file
diff --git a/plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/util/ObservableAtomicReference.java b/plugins/net.mograsim.logic.model/src/net/mograsim/logic/model/util/ObservableAtomicReference.java
new file mode 100644 (file)
index 0000000..0e180f1
--- /dev/null
@@ -0,0 +1,92 @@
+package net.mograsim.logic.model.util;
+
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.UnaryOperator;
+
+public class ObservableAtomicReference<V>
+{
+       private final AtomicReference<V> ref;
+
+       private final List<Consumer<ObservableAtomicReference<V>>> observers;
+
+       public ObservableAtomicReference()
+       {
+               ref = new AtomicReference<>();
+               observers = new ArrayList<>();
+       }
+
+       public ObservableAtomicReference(V initialValue)
+       {
+               ref = new AtomicReference<>(initialValue);
+               observers = new ArrayList<>();
+       }
+
+       /**
+        * Returns the current value, with memory effects as specified by {@link VarHandle#getVolatile}.
+        *
+        * @return the current value
+        */
+       public V get()
+       {
+               return ref.get();
+       }
+
+       /**
+        * Sets the value to {@code newValue}, with memory effects as specified by {@link VarHandle#setVolatile}.
+        *
+        * @param newValue the new value
+        */
+       public void set(V newValue)
+       {
+               ref.set(newValue);
+               callObservers();
+       }
+
+       /**
+        * Atomically sets the value to {@code newValue} and returns the old value, with memory effects as specified by
+        * {@link VarHandle#getAndSet}.
+        *
+        * @param newValue the new value
+        * @return the previous value
+        */
+       public V getAndSet(V newValue)
+       {
+               V oldValue = ref.getAndSet(newValue);
+               callObservers();
+               return oldValue;
+       }
+
+       /**
+        * Atomically updates (with memory effects as specified by {@link VarHandle#compareAndSet}) the current value with the results of
+        * applying the given function, returning the updated value. The function should be side-effect-free, since it may be re-applied when
+        * attempted updates fail due to contention among threads.
+        *
+        * @param updateFunction a side-effect-free function
+        * @return the updated value
+        */
+       public V updateAndGet(UnaryOperator<V> updateFunction)
+       {
+               V updatedValue = ref.updateAndGet(updateFunction);
+               callObservers();
+               return updatedValue;
+       }
+
+       public void addObserver(Consumer<ObservableAtomicReference<V>> obs)
+       {
+               observers.add(obs);
+       }
+
+       public void removeObserver(Consumer<ObservableAtomicReference<V>> obs)
+       {
+               observers.add(obs);
+       }
+
+       private void callObservers()
+       {
+               observers.forEach(o -> o.accept(this));
+       }
+}
\ No newline at end of file
index 8325068..7432843 100644 (file)
@@ -15,6 +15,7 @@ public interface Machine
 
        LogicModel getModel();
 
+       // TODO replace with HLS references
        CoreClock getClock();
 
        BitVector getRegister(Register r);
index 706734f..25c0b68 100644 (file)
@@ -1,5 +1,9 @@
 package net.mograsim.machine.mi.components;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
 import net.mograsim.logic.model.model.LogicModelModifiable;
 import net.mograsim.logic.model.model.wires.Pin;
 import net.mograsim.logic.model.model.wires.PinUsage;
@@ -16,6 +20,8 @@ public abstract class ModelMicroInstructionMemory extends ModelMemory
        private CoreMicroInstructionMemory memory;
        private final MicroInstructionMemoryDefinition definition;
 
+       private final List<Consumer<Object>> memoryBindingListeners;
+
        public ModelMicroInstructionMemory(LogicModelModifiable model, MicroInstructionMemoryDefinition definition, String name)
        {
                super(model, 120, 150, name, "MPM", false);
@@ -24,10 +30,12 @@ public abstract class ModelMicroInstructionMemory extends ModelMemory
                addPin(dataPin = new Pin(model, this, "D", definition.getMicroInstructionDefinition().sizeInBits(), PinUsage.OUTPUT, getWidth(),
                                50));
 
+               memoryBindingListeners = new ArrayList<>();
+
                setHighLevelStateHandler(new HighLevelStateHandler()
                {
                        @Override
-                       public Object getHighLevelState(String stateID)
+                       public Object get(String stateID)
                        {
                                if (stateID.equals("memory_binding"))
                                        return memory.getMemory();
@@ -35,10 +43,30 @@ public abstract class ModelMicroInstructionMemory extends ModelMemory
                        }
 
                        @Override
-                       public void setHighLevelState(String stateID, Object newState)
+                       public void set(String stateID, Object newState)
                        {
                                if (stateID.equals("memory_binding"))
+                               {
                                        memory.setMemory((MicroInstructionMemory) newState);
+                                       memoryBindingListeners.forEach(l -> l.accept(newState));
+                               } else
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                       }
+
+                       @Override
+                       public void addListener(String stateID, Consumer<Object> stateChanged)
+                       {
+                               if (stateID.equals("memory_binding"))
+                                       memoryBindingListeners.add(stateChanged);
+                               else
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                       }
+
+                       @Override
+                       public void removeListener(String stateID, Consumer<Object> stateChanged)
+                       {
+                               if (stateID.equals("memory_binding"))
+                                       memoryBindingListeners.remove(stateChanged);
                                else
                                        throw new IllegalArgumentException("No high level state with ID " + stateID);
                        }
index b249849..a1112e6 100644 (file)
@@ -1,5 +1,9 @@
 package net.mograsim.machine.standard.memory;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
 import net.mograsim.logic.model.model.LogicModelModifiable;
 import net.mograsim.logic.model.model.wires.Pin;
 import net.mograsim.logic.model.model.wires.PinUsage;
@@ -16,6 +20,8 @@ public abstract class ModelWordAddressableMemory extends ModelMemory
        private CoreWordAddressableMemory memory;
        private MainMemoryDefinition definition;
 
+       private final List<Consumer<Object>> memoryBindingListeners;
+
        public ModelWordAddressableMemory(LogicModelModifiable model, MainMemoryDefinition definition, String name)
        {
                super(model, 120, 150, name, "RAM", false);
@@ -25,10 +31,12 @@ public abstract class ModelWordAddressableMemory extends ModelMemory
                addPin(dataPin = new Pin(model, this, "D", definition.getCellWidth(), PinUsage.TRISTATE, getWidth(), 50));
                addPin(rWPin = new Pin(model, this, "RW", 1, PinUsage.INPUT, getWidth(), 70));
 
+               memoryBindingListeners = new ArrayList<>();
+
                setHighLevelStateHandler(new HighLevelStateHandler()
                {
                        @Override
-                       public Object getHighLevelState(String stateID)
+                       public Object get(String stateID)
                        {
                                if (stateID.equals("memory_binding"))
                                        return memory.getMemory();
@@ -36,10 +44,30 @@ public abstract class ModelWordAddressableMemory extends ModelMemory
                        }
 
                        @Override
-                       public void setHighLevelState(String stateID, Object newState)
+                       public void set(String stateID, Object newState)
                        {
                                if (stateID.equals("memory_binding"))
+                               {
                                        memory.setMemory((MainMemory) newState);
+                                       memoryBindingListeners.forEach(l -> l.accept(newState));
+                               } else
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                       }
+
+                       @Override
+                       public void addListener(String stateID, Consumer<Object> stateChanged)
+                       {
+                               if (stateID.equals("memory_binding"))
+                                       memoryBindingListeners.add(stateChanged);
+                               else
+                                       throw new IllegalArgumentException("No high level state with ID " + stateID);
+                       }
+
+                       @Override
+                       public void removeListener(String stateID, Consumer<Object> stateChanged)
+                       {
+                               if (stateID.equals("memory_binding"))
+                                       memoryBindingListeners.remove(stateChanged);
                                else
                                        throw new IllegalArgumentException("No high level state with ID " + stateID);
                        }