HighLevelStates now support adding/removing listeners
[Mograsim.git] / plugins / net.mograsim.logic.model / src / net / mograsim / logic / model / model / components / atomic / SimpleRectangularHardcodedModelComponent.java
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;