1 package net.mograsim.logic.model.am2900;
\r
3 import static org.junit.jupiter.api.Assertions.fail;
\r
5 import java.lang.reflect.Field;
\r
6 import java.util.HashMap;
\r
7 import java.util.HashSet;
\r
8 import java.util.LinkedList;
\r
9 import java.util.Objects;
\r
10 import java.util.Queue;
\r
11 import java.util.Set;
\r
12 import java.util.TreeSet;
\r
14 import net.mograsim.logic.core.components.BitDisplay;
\r
15 import net.mograsim.logic.core.components.ManualSwitch;
\r
16 import net.mograsim.logic.core.timeline.Timeline;
\r
17 import net.mograsim.logic.model.am2900.TestableCircuit.Result;
\r
18 import net.mograsim.logic.model.model.ViewModel;
\r
19 import net.mograsim.logic.model.model.ViewModelModifiable;
\r
20 import net.mograsim.logic.model.model.components.GUIComponent;
\r
21 import net.mograsim.logic.model.model.components.atomic.GUIBitDisplay;
\r
22 import net.mograsim.logic.model.model.components.atomic.GUIManualSwitch;
\r
23 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
\r
24 import net.mograsim.logic.model.model.wires.GUIWire;
\r
25 import net.mograsim.logic.model.model.wires.Pin;
\r
26 import net.mograsim.logic.model.modeladapter.LogicModelParameters;
\r
27 import net.mograsim.logic.model.modeladapter.ViewLogicModelAdapter;
\r
28 import net.mograsim.logic.model.serializing.IndirectGUIComponentCreator;
\r
29 import net.mograsim.logic.model.util.ModellingTool;
\r
31 public class TestEnvironmentHelper
\r
33 private final TestableCircuit testEnvInstance;
\r
34 private final Class<?> testEnvClass;
\r
35 private final String modelId;
\r
36 private Field componentField;
\r
37 private Field timelineField;
\r
39 private GUIComponent component;
\r
40 private Timeline timeline;
\r
41 private ViewModelModifiable viewModel;
\r
42 private ModellingTool modellingTool;
\r
43 private HashMap<String, GUIManualSwitch> idSwitchMap = new HashMap<>();
\r
44 private HashMap<String, GUIBitDisplay> idDisplayMap = new HashMap<>();
\r
46 private DebugState debug = DebugState.NO_DEBUG;
\r
47 private Set<String> wireDebugChangeSet;
\r
48 private boolean debugWires = false;
\r
49 public int debugEventThreshold = 10_000;
\r
50 public int debugEventCount = 500;
\r
51 private int eventCounter;
\r
53 public TestEnvironmentHelper(TestableCircuit testEnv, String modelId)
\r
55 this.testEnvInstance = testEnv;
\r
56 this.modelId = modelId;
\r
57 this.testEnvClass = testEnvInstance.getClass();
\r
58 for (Field f : testEnvClass.getDeclaredFields())
\r
60 if (GUIComponent.class.isAssignableFrom(f.getType()))
\r
63 componentField.setAccessible(true);
\r
64 } else if (Timeline.class.isAssignableFrom(f.getType()))
\r
67 timelineField.setAccessible(true);
\r
70 if (componentField == null || timelineField == null)
\r
71 throw new IllegalStateException("No component or timeline field found!");
\r
74 public void setup(DebugState debug)
\r
77 // Create view model
\r
78 viewModel = new ViewModelModifiable();
\r
79 modellingTool = ModellingTool.createFor(viewModel);
\r
80 component = IndirectGUIComponentCreator.createComponent(viewModel, modelId);
\r
81 setField(componentField, component);
\r
83 component.getPins().values().forEach(this::extendModelPin);
\r
85 // Create logic model
\r
86 LogicModelParameters params = new LogicModelParameters();
\r
87 params.gateProcessTime = 50;
\r
88 params.wireTravelTime = 10;
\r
89 timeline = ViewLogicModelAdapter.convert(viewModel, params);
\r
90 setField(timelineField, timeline);
\r
92 // Bind switches/displays to this test class
\r
93 component.getPins().values().forEach(this::bindModelPin);
\r
95 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
\r
99 timeline.addEventAddedListener(te -> eventCounter++);
\r
102 private void extendModelPin(Pin p)
\r
104 String javaIdentId = idToJavaIdentifier(p.name);
\r
107 Field f = testEnvClass.getDeclaredField(javaIdentId);
\r
108 Class<?> type = f.getType();
\r
109 if (ManualSwitch.class.isAssignableFrom(type))
\r
111 GUIManualSwitch gms = new GUIManualSwitch(viewModel, p.logicWidth);
\r
112 modellingTool.connect(p, gms.getOutputPin());
\r
113 idSwitchMap.put(p.name, gms);
\r
114 } else if (BitDisplay.class.isAssignableFrom(type))
\r
116 GUIBitDisplay gbd = new GUIBitDisplay(viewModel, p.logicWidth);
\r
117 modellingTool.connect(p, gbd.getInputPin());
\r
118 idDisplayMap.put(p.name, gbd);
\r
121 fail("unkown field type " + type);
\r
124 catch (NoSuchFieldException | SecurityException e)
\r
130 private void bindModelPin(Pin p)
\r
132 String javaIdentId = idToJavaIdentifier(p.name);
\r
133 if (idDisplayMap.containsKey(p.name))
\r
134 setField(javaIdentId, idDisplayMap.get(p.name).getBitDisplay());
\r
135 if (idSwitchMap.containsKey(p.name))
\r
136 setField(javaIdentId, idSwitchMap.get(p.name).getManualSwitch());
\r
139 private void setupDebugging()
\r
142 HashSet<GUIWire> wiresIncludingSubmodels = new HashSet<>();
\r
143 Queue<ViewModel> modelsToIterate = new LinkedList<>();
\r
144 modelsToIterate.add(viewModel);
\r
145 while (modelsToIterate.size() > 0)
\r
147 ViewModel model = modelsToIterate.poll();
\r
148 wiresIncludingSubmodels.addAll(model.getWiresByName().values());
\r
149 for (GUIComponent comp : model.getComponentsByName().values())
\r
150 if (comp instanceof SubmodelComponent)
\r
151 modelsToIterate.offer(((SubmodelComponent) comp).submodel);
\r
153 System.out.println(wiresIncludingSubmodels.size());
\r
154 viewModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w ->
\r
158 wireDebugChangeSet.add(w.toString());
\r
163 public Result run()
\r
165 // Normal execution until completion or eventLimit
\r
166 int eventLimit = debugEventThreshold;
\r
168 debugWires = false;
\r
169 while (eventCounter < eventLimit)
\r
171 timeline.executeNext();
\r
172 if (!timeline.hasNext())
\r
173 return Result.SUCCESS;
\r
176 // Start debugging if event limit is reached (if debug is active)
\r
177 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
\r
178 return debugThisRun();
\r
180 return Result.OUT_OF_TIME;
\r
183 private Result debugThisRun()
\r
185 int eventLimit = debugEventThreshold;
\r
187 wireDebugChangeSet = new TreeSet<>();
\r
188 Set<String> oldChangeSet;
\r
189 // observe wire changes to detect, if we are really stuck in an endless loop
\r
192 eventLimit += debugEventCount;
\r
193 oldChangeSet = wireDebugChangeSet;
\r
194 wireDebugChangeSet = new TreeSet<>();
\r
195 while (eventCounter < eventLimit)
\r
197 timeline.executeNext();
\r
198 if (!timeline.hasNext())
\r
200 // no endless loop, but more events needed than expected
\r
201 System.out.println("run() took longer than expected: " + eventCounter);
\r
202 return Result.SUCCESS;
\r
205 } while (!oldChangeSet.equals(wireDebugChangeSet));
\r
206 // if stuck, abort execution and print wires
\r
207 System.err.print("Problematic Wire updates:");
\r
208 wireDebugChangeSet.forEach(System.out::println);
\r
209 System.err.println("run() failed: " + eventCounter);
\r
210 return Result.OUT_OF_TIME;
\r
213 private static String idToJavaIdentifier(String s)
\r
215 StringBuilder sb = new StringBuilder(s.length());
\r
216 char c = s.charAt(0);
\r
217 sb.append(Character.isJavaIdentifierStart(c) ? c : '_');
\r
218 for (int i = 1; i < s.length(); i++)
\r
219 sb.append(Character.isJavaIdentifierPart(c = s.charAt(i)) ? c : '_');
\r
220 return sb.toString();
\r
223 private <S> void setField(Field f, S value)
\r
227 f.set(testEnvInstance, Objects.requireNonNull(value));
\r
229 catch (Exception e)
\r
235 private <S> void setField(String name, S value)
\r
239 Field f = testEnvClass.getDeclaredField(name);
\r
240 f.setAccessible(true);
\r
241 f.set(testEnvInstance, Objects.requireNonNull(value));
\r
243 catch (Exception e)
\r
249 public enum DebugState
\r
251 NO_DEBUG, DEBUG_AT_PERFORMANCE_COST;
\r