1 package net.mograsim.logic.model.am2900.util;
3 import static org.junit.jupiter.api.Assertions.fail;
5 import java.lang.reflect.Field;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.LinkedList;
9 import java.util.Objects;
10 import java.util.Optional;
11 import java.util.Queue;
13 import java.util.TreeSet;
15 import net.mograsim.logic.core.components.BitDisplay;
16 import net.mograsim.logic.core.components.ManualSwitch;
17 import net.mograsim.logic.core.timeline.Timeline;
18 import net.mograsim.logic.model.LogicUIStandaloneGUI;
19 import net.mograsim.logic.model.am2900.Am2900Loader;
20 import net.mograsim.logic.model.am2900.TestableCircuit;
21 import net.mograsim.logic.model.am2900.TestableCircuit.Result;
22 import net.mograsim.logic.model.model.ViewModel;
23 import net.mograsim.logic.model.model.ViewModelModifiable;
24 import net.mograsim.logic.model.model.components.GUIComponent;
25 import net.mograsim.logic.model.model.components.atomic.GUIBitDisplay;
26 import net.mograsim.logic.model.model.components.atomic.GUIManualSwitch;
27 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
28 import net.mograsim.logic.model.model.wires.GUIWire;
29 import net.mograsim.logic.model.model.wires.Pin;
30 import net.mograsim.logic.model.modeladapter.LogicModelParameters;
31 import net.mograsim.logic.model.modeladapter.ViewLogicModelAdapter;
32 import net.mograsim.logic.model.serializing.IndirectGUIComponentCreator;
33 import net.mograsim.logic.model.util.ModellingTool;
35 public class TestEnvironmentHelper
37 private final TestableCircuit testEnvInstance;
38 private final Class<?> testEnvClass;
39 private final String modelId;
40 private Field componentField;
41 private Optional<Field> timelineField = Optional.empty();
43 private GUIComponent component;
44 private Timeline timeline;
45 private ViewModelModifiable viewModel;
46 private ModellingTool modellingTool;
47 private HashMap<String, GUIManualSwitch> idSwitchMap = new HashMap<>();
48 private HashMap<String, GUIBitDisplay> idDisplayMap = new HashMap<>();
50 private DebugState debug = DebugState.NO_DEBUG;
51 private Set<String> wireDebugChangeSet;
52 private boolean debugWires = false;
53 public int debugEventThreshold = 10_000;
54 public int debugEventCount = 500;
55 private int eventCounter;
57 public TestEnvironmentHelper(TestableCircuit testEnv, String modelId)
59 this.testEnvInstance = testEnv;
60 this.modelId = modelId;
61 this.testEnvClass = testEnvInstance.getClass();
62 for (Field f : testEnvClass.getDeclaredFields())
64 if (GUIComponent.class.isAssignableFrom(f.getType()))
67 componentField.setAccessible(true);
68 } else if (Timeline.class.isAssignableFrom(f.getType()))
70 f.setAccessible(true);
71 timelineField = Optional.of(f);
74 if (componentField == null)
75 throw new IllegalStateException("No component or timeline field found!");
78 public void setup(DebugState debug)
82 viewModel = new ViewModelModifiable();
83 modellingTool = ModellingTool.createFor(viewModel);
85 component = IndirectGUIComponentCreator.createComponent(viewModel, modelId);
86 setField(componentField, component);
88 component.getPins().values().forEach(this::extendModelPin);
91 LogicModelParameters params = new LogicModelParameters();
92 params.gateProcessTime = 50;
93 params.wireTravelTime = 10;
94 timeline = ViewLogicModelAdapter.convert(viewModel, params);
95 timelineField.ifPresent(f -> setField(f, timeline));
97 // Bind switches/displays to this test class
98 component.getPins().values().forEach(this::bindModelPin);
100 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
104 timeline.addEventAddedListener(te -> eventCounter++);
107 private void extendModelPin(Pin p)
109 String javaIdentId = idToJavaIdentifier(p.name);
112 Field f = testEnvClass.getDeclaredField(javaIdentId);
113 Class<?> type = f.getType();
114 if (ManualSwitch.class.isAssignableFrom(type))
116 GUIManualSwitch gms = new GUIManualSwitch(viewModel, p.logicWidth);
117 modellingTool.connect(p, gms.getOutputPin());
118 idSwitchMap.put(p.name, gms);
119 } else if (BitDisplay.class.isAssignableFrom(type))
121 GUIBitDisplay gbd = new GUIBitDisplay(viewModel, p.logicWidth);
122 modellingTool.connect(p, gbd.getInputPin());
123 idDisplayMap.put(p.name, gbd);
124 } else if (SwitchWithDisplay.class.isAssignableFrom(type))
126 SwitchWithDisplay swd = new SwitchWithDisplay(viewModel, p);
130 fail("unkown field type " + type);
133 catch (NoSuchFieldException | SecurityException e)
139 private void bindModelPin(Pin p)
141 String javaIdentId = idToJavaIdentifier(p.name);
142 if (idDisplayMap.containsKey(p.name))
143 setField(javaIdentId, idDisplayMap.get(p.name).getBitDisplay());
144 if (idSwitchMap.containsKey(p.name))
145 setField(javaIdentId, idSwitchMap.get(p.name).getManualSwitch());
148 private void setupDebugging()
151 HashSet<GUIWire> wiresIncludingSubmodels = new HashSet<>();
152 Queue<ViewModel> modelsToIterate = new LinkedList<>();
153 modelsToIterate.add(viewModel);
154 while (modelsToIterate.size() > 0)
156 ViewModel model = modelsToIterate.poll();
157 wiresIncludingSubmodels.addAll(model.getWiresByName().values());
158 for (GUIComponent comp : model.getComponentsByName().values())
159 if (comp instanceof SubmodelComponent)
160 modelsToIterate.offer(((SubmodelComponent) comp).submodel);
162 System.out.println(wiresIncludingSubmodels.size());
163 viewModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w ->
167 wireDebugChangeSet.add(w.toString());
174 // Normal execution until completion or eventLimit
175 int eventLimit = debugEventThreshold;
178 while (eventCounter < eventLimit)
180 timeline.executeNext();
181 if (!timeline.hasNext())
182 return Result.SUCCESS;
185 // Start debugging if event limit is reached (if debug is active)
186 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
187 return debugThisRun();
189 return Result.OUT_OF_TIME;
192 private Result debugThisRun()
194 int eventLimit = debugEventThreshold;
196 wireDebugChangeSet = new TreeSet<>();
197 Set<String> oldChangeSet;
198 // observe wire changes to detect, if we are really stuck in an endless loop
201 eventLimit += debugEventCount;
202 oldChangeSet = wireDebugChangeSet;
203 wireDebugChangeSet = new TreeSet<>();
204 while (eventCounter < eventLimit)
206 timeline.executeNext();
207 if (!timeline.hasNext())
209 // no endless loop, but more events needed than expected
210 System.out.println("run() took longer than expected: " + eventCounter);
211 return Result.SUCCESS;
214 } while (!oldChangeSet.equals(wireDebugChangeSet));
215 // if stuck, abort execution and print wires
216 System.err.print("Problematic Wire updates:");
217 wireDebugChangeSet.forEach(System.out::println);
218 System.err.println("run() failed: " + eventCounter);
219 return Result.OUT_OF_TIME;
222 private static String idToJavaIdentifier(String s)
224 StringBuilder sb = new StringBuilder(s.length());
225 char c = s.charAt(0);
226 sb.append(Character.isJavaIdentifierStart(c) ? c : '_');
227 for (int i = 1; i < s.length(); i++)
228 sb.append(Character.isJavaIdentifierPart(c = s.charAt(i)) ? c : '_');
229 return sb.toString();
232 private <S> void setField(Field f, S value)
236 f.setAccessible(true);
237 f.set(testEnvInstance, Objects.requireNonNull(value));
245 private <S> void setField(String name, S value)
249 Field f = testEnvClass.getDeclaredField(name);
250 f.setAccessible(true);
251 f.set(testEnvInstance, Objects.requireNonNull(value));
259 public void displayState()
263 new LogicUIStandaloneGUI(viewModel).run();
264 viewModel.setRedrawHandler(null);
272 public enum DebugState
274 NO_DEBUG, DEBUG_AT_PERFORMANCE_COST;