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.TestableCircuit;
20 import net.mograsim.logic.model.am2900.TestableCircuit.Result;
21 import net.mograsim.logic.model.model.ViewModel;
22 import net.mograsim.logic.model.model.ViewModelModifiable;
23 import net.mograsim.logic.model.model.components.GUIComponent;
24 import net.mograsim.logic.model.model.components.atomic.GUIBitDisplay;
25 import net.mograsim.logic.model.model.components.atomic.GUIManualSwitch;
26 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
27 import net.mograsim.logic.model.model.wires.GUIWire;
28 import net.mograsim.logic.model.model.wires.Pin;
29 import net.mograsim.logic.model.modeladapter.LogicModelParameters;
30 import net.mograsim.logic.model.modeladapter.ViewLogicModelAdapter;
31 import net.mograsim.logic.model.serializing.IndirectGUIComponentCreator;
32 import net.mograsim.logic.model.util.ModellingTool;
34 public class TestEnvironmentHelper
36 private final TestableCircuit testEnvInstance;
37 private final Class<?> testEnvClass;
38 private final String modelId;
39 private Field componentField;
40 private Optional<Field> timelineField = Optional.empty();
42 private GUIComponent component;
43 private Timeline timeline;
44 private ViewModelModifiable viewModel;
45 private ModellingTool modellingTool;
46 private HashMap<String, GUIManualSwitch> idSwitchMap = new HashMap<>();
47 private HashMap<String, GUIBitDisplay> idDisplayMap = new HashMap<>();
49 private DebugState debug = DebugState.NO_DEBUG;
50 private Set<String> wireDebugChangeSet;
51 private boolean debugWires = false;
52 public int debugEventThreshold = 10_000;
53 public int debugEventCount = 500;
54 private int eventCounter;
56 public TestEnvironmentHelper(TestableCircuit testEnv, String modelId)
58 this.testEnvInstance = testEnv;
59 this.modelId = modelId;
60 this.testEnvClass = testEnvInstance.getClass();
61 for (Field f : testEnvClass.getDeclaredFields())
63 if (GUIComponent.class.isAssignableFrom(f.getType()))
66 componentField.setAccessible(true);
67 } else if (Timeline.class.isAssignableFrom(f.getType()))
69 f.setAccessible(true);
70 timelineField = Optional.of(f);
73 if (componentField == null)
74 throw new IllegalStateException("No component or timeline field found!");
77 public void setup(DebugState debug)
81 viewModel = new ViewModelModifiable();
82 modellingTool = ModellingTool.createFor(viewModel);
83 component = IndirectGUIComponentCreator.createComponent(viewModel, modelId);
84 setField(componentField, component);
86 component.getPins().values().forEach(this::extendModelPin);
89 LogicModelParameters params = new LogicModelParameters();
90 params.gateProcessTime = 50;
91 params.wireTravelTime = 10;
92 timeline = ViewLogicModelAdapter.convert(viewModel, params);
93 timelineField.ifPresent(f -> setField(f, timeline));
95 // Bind switches/displays to this test class
96 component.getPins().values().forEach(this::bindModelPin);
98 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
102 timeline.addEventAddedListener(te -> eventCounter++);
105 private void extendModelPin(Pin p)
107 String javaIdentId = idToJavaIdentifier(p.name);
110 Field f = testEnvClass.getDeclaredField(javaIdentId);
111 Class<?> type = f.getType();
112 if (ManualSwitch.class.isAssignableFrom(type))
114 GUIManualSwitch gms = new GUIManualSwitch(viewModel, p.logicWidth);
115 modellingTool.connect(p, gms.getOutputPin());
116 idSwitchMap.put(p.name, gms);
117 } else if (BitDisplay.class.isAssignableFrom(type))
119 GUIBitDisplay gbd = new GUIBitDisplay(viewModel, p.logicWidth);
120 modellingTool.connect(p, gbd.getInputPin());
121 idDisplayMap.put(p.name, gbd);
122 } else if (SwitchWithDisplay.class.isAssignableFrom(type))
124 SwitchWithDisplay swd = new SwitchWithDisplay(viewModel, p);
128 fail("unkown field type " + type);
131 catch (NoSuchFieldException | SecurityException e)
137 private void bindModelPin(Pin p)
139 String javaIdentId = idToJavaIdentifier(p.name);
140 if (idDisplayMap.containsKey(p.name))
141 setField(javaIdentId, idDisplayMap.get(p.name).getBitDisplay());
142 if (idSwitchMap.containsKey(p.name))
143 setField(javaIdentId, idSwitchMap.get(p.name).getManualSwitch());
146 private void setupDebugging()
149 HashSet<GUIWire> wiresIncludingSubmodels = new HashSet<>();
150 Queue<ViewModel> modelsToIterate = new LinkedList<>();
151 modelsToIterate.add(viewModel);
152 while (modelsToIterate.size() > 0)
154 ViewModel model = modelsToIterate.poll();
155 wiresIncludingSubmodels.addAll(model.getWiresByName().values());
156 for (GUIComponent comp : model.getComponentsByName().values())
157 if (comp instanceof SubmodelComponent)
158 modelsToIterate.offer(((SubmodelComponent) comp).submodel);
160 System.out.println(wiresIncludingSubmodels.size());
161 viewModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w ->
165 wireDebugChangeSet.add(w.toString());
172 // Normal execution until completion or eventLimit
173 int eventLimit = debugEventThreshold;
176 while (eventCounter < eventLimit)
178 timeline.executeNext();
179 if (!timeline.hasNext())
180 return Result.SUCCESS;
183 // Start debugging if event limit is reached (if debug is active)
184 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
185 return debugThisRun();
187 return Result.OUT_OF_TIME;
190 private Result debugThisRun()
192 int eventLimit = debugEventThreshold;
194 wireDebugChangeSet = new TreeSet<>();
195 Set<String> oldChangeSet;
196 // observe wire changes to detect, if we are really stuck in an endless loop
199 eventLimit += debugEventCount;
200 oldChangeSet = wireDebugChangeSet;
201 wireDebugChangeSet = new TreeSet<>();
202 while (eventCounter < eventLimit)
204 timeline.executeNext();
205 if (!timeline.hasNext())
207 // no endless loop, but more events needed than expected
208 System.out.println("run() took longer than expected: " + eventCounter);
209 return Result.SUCCESS;
212 } while (!oldChangeSet.equals(wireDebugChangeSet));
213 // if stuck, abort execution and print wires
214 System.err.print("Problematic Wire updates:");
215 wireDebugChangeSet.forEach(System.out::println);
216 System.err.println("run() failed: " + eventCounter);
217 return Result.OUT_OF_TIME;
220 private static String idToJavaIdentifier(String s)
222 StringBuilder sb = new StringBuilder(s.length());
223 char c = s.charAt(0);
224 sb.append(Character.isJavaIdentifierStart(c) ? c : '_');
225 for (int i = 1; i < s.length(); i++)
226 sb.append(Character.isJavaIdentifierPart(c = s.charAt(i)) ? c : '_');
227 return sb.toString();
230 private <S> void setField(Field f, S value)
234 f.setAccessible(true);
235 f.set(testEnvInstance, Objects.requireNonNull(value));
243 private <S> void setField(String name, S value)
247 Field f = testEnvClass.getDeclaredField(name);
248 f.setAccessible(true);
249 f.set(testEnvInstance, Objects.requireNonNull(value));
257 public void displayState()
261 new LogicUIStandaloneGUI(viewModel).run();
262 viewModel.setRedrawHandler(null);
270 public enum DebugState
272 NO_DEBUG, DEBUG_AT_PERFORMANCE_COST;