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.CoreBitDisplay;
16 import net.mograsim.logic.core.components.CoreManualSwitch;
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.LogicModel;
23 import net.mograsim.logic.model.model.LogicModelModifiable;
24 import net.mograsim.logic.model.model.components.ModelComponent;
25 import net.mograsim.logic.model.model.components.atomic.ModelBitDisplay;
26 import net.mograsim.logic.model.model.components.atomic.ModelManualSwitch;
27 import net.mograsim.logic.model.model.components.submodels.SubmodelComponent;
28 import net.mograsim.logic.model.model.wires.ModelWire;
29 import net.mograsim.logic.model.model.wires.Pin;
30 import net.mograsim.logic.model.modeladapter.CoreModelParameters;
31 import net.mograsim.logic.model.modeladapter.CoreModelParameters.CoreModelParametersBuilder;
32 import net.mograsim.logic.model.modeladapter.LogicCoreAdapter;
33 import net.mograsim.logic.model.preferences.DefaultRenderPreferences;
34 import net.mograsim.logic.model.serializing.IndirectModelComponentCreator;
35 import net.mograsim.logic.model.util.ModellingTool;
37 public class TestEnvironmentHelper
39 private final TestableCircuit testEnvInstance;
40 private final Class<?> testEnvClass;
41 private final String modelId;
42 private Field componentField;
43 private Optional<Field> timelineField = Optional.empty();
45 private ModelComponent component;
46 private Timeline timeline;
47 private LogicModelModifiable logicModel;
48 private ModellingTool modellingTool;
49 private HashMap<String, ModelManualSwitch> idSwitchMap = new HashMap<>();
50 private HashMap<String, ModelBitDisplay> idDisplayMap = new HashMap<>();
52 private DebugState debug = DebugState.NO_DEBUG;
53 private Set<String> wireDebugChangeSet;
54 private boolean debugWires = false;
55 public int debugEventThreshold = 20_000;
56 public int debugEventCount = 500;
57 private int eventCounter;
59 public TestEnvironmentHelper(TestableCircuit testEnv, String modelId)
61 this.testEnvInstance = testEnv;
62 this.modelId = modelId;
63 this.testEnvClass = testEnvInstance.getClass();
64 for (Field f : testEnvClass.getDeclaredFields())
66 if (ModelComponent.class.isAssignableFrom(f.getType()))
69 componentField.setAccessible(true);
70 } else if (Timeline.class.isAssignableFrom(f.getType()))
72 f.setAccessible(true);
73 timelineField = Optional.of(f);
76 if (componentField == null)
77 throw new IllegalStateException("No component or timeline field found!");
80 public void setup(DebugState debug)
84 logicModel = new LogicModelModifiable();
85 modellingTool = ModellingTool.createFor(logicModel);
87 component = IndirectModelComponentCreator.createComponent(logicModel, modelId);
88 setField(componentField, component);
90 component.getPins().values().forEach(this::extendModelPin);
93 CoreModelParametersBuilder paramsBuilder = CoreModelParameters.builder();
94 paramsBuilder.gateProcessTime = 50;
95 paramsBuilder.hardcodedComponentProcessTime = paramsBuilder.gateProcessTime * 5;
96 paramsBuilder.wireTravelTime = 10;
97 timeline = LogicCoreAdapter.convert(logicModel, paramsBuilder.build());
98 timelineField.ifPresent(f -> setField(f, timeline));
100 // Bind switches/displays to this test class
101 component.getPins().values().forEach(this::bindModelPin);
103 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
107 timeline.addEventAddedListener(te -> eventCounter++);
110 private void extendModelPin(Pin p)
112 String javaIdentId = idToJavaIdentifier(p.name);
115 Field f = testEnvClass.getDeclaredField(javaIdentId);
116 Class<?> type = f.getType();
117 if (CoreManualSwitch.class.isAssignableFrom(type))
119 ModelManualSwitch gms = new ModelManualSwitch(logicModel, p.logicWidth);
120 modellingTool.connect(p, gms.getOutputPin());
121 idSwitchMap.put(p.name, gms);
122 } else if (CoreBitDisplay.class.isAssignableFrom(type))
124 ModelBitDisplay gbd = new ModelBitDisplay(logicModel, p.logicWidth);
125 modellingTool.connect(p, gbd.getInputPin());
126 idDisplayMap.put(p.name, gbd);
127 } else if (SwitchWithDisplay.class.isAssignableFrom(type))
129 SwitchWithDisplay swd = new SwitchWithDisplay(logicModel, p);
133 fail("unkown field type " + type);
136 catch (NoSuchFieldException | SecurityException e)
142 private void bindModelPin(Pin p)
144 String javaIdentId = idToJavaIdentifier(p.name);
145 if (idDisplayMap.containsKey(p.name))
146 setField(javaIdentId, idDisplayMap.get(p.name).getBitDisplay());
147 if (idSwitchMap.containsKey(p.name))
148 setField(javaIdentId, idSwitchMap.get(p.name).getManualSwitch());
151 private void setupDebugging()
154 HashSet<ModelWire> wiresIncludingSubmodels = new HashSet<>();
155 Queue<LogicModel> modelsToIterate = new LinkedList<>();
156 modelsToIterate.add(logicModel);
157 while (modelsToIterate.size() > 0)
159 LogicModel model = modelsToIterate.poll();
160 wiresIncludingSubmodels.addAll(model.getWiresByName().values());
161 for (ModelComponent comp : model.getComponentsByName().values())
162 if (comp instanceof SubmodelComponent)
163 modelsToIterate.offer(((SubmodelComponent) comp).submodel);
165 System.out.println(wiresIncludingSubmodels.size());
166 logicModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w ->
170 wireDebugChangeSet.add(w.toString());
177 // Normal execution until completion or eventLimit
178 int eventLimit = debugEventThreshold;
181 while (eventCounter < eventLimit)
183 timeline.executeNext();
184 if (!timeline.hasNext())
185 return Result.SUCCESS;
188 // Start debugging if event limit is reached (if debug is active)
189 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
190 return debugThisRun();
192 return Result.OUT_OF_TIME;
195 private Result debugThisRun()
197 int eventLimit = debugEventThreshold;
199 wireDebugChangeSet = new TreeSet<>();
200 Set<String> oldChangeSet;
201 // observe wire changes to detect, if we are really stuck in an endless loop
204 eventLimit += debugEventCount;
205 oldChangeSet = wireDebugChangeSet;
206 wireDebugChangeSet = new TreeSet<>();
207 while (eventCounter < eventLimit)
209 timeline.executeNext();
210 if (!timeline.hasNext())
212 // no endless loop, but more events needed than expected
213 System.out.println("run() took longer than expected: " + eventCounter);
214 return Result.SUCCESS;
217 } while (!oldChangeSet.equals(wireDebugChangeSet));
218 // if stuck, abort execution and print wires
219 System.err.print("Problematic Wire updates:");
220 wireDebugChangeSet.forEach(System.out::println);
221 System.err.println("run() failed: " + eventCounter);
222 return Result.OUT_OF_TIME;
225 private static String idToJavaIdentifier(String s)
227 StringBuilder sb = new StringBuilder(s.length());
228 char c = s.charAt(0);
229 sb.append(Character.isJavaIdentifierStart(c) ? c : '_');
230 for (int i = 1; i < s.length(); i++)
231 sb.append(Character.isJavaIdentifierPart(c = s.charAt(i)) ? c : '_');
232 return sb.toString();
235 private <S> void setField(Field f, S value)
239 f.setAccessible(true);
240 f.set(testEnvInstance, Objects.requireNonNull(value));
248 private <S> void setField(String name, S value)
252 Field f = testEnvClass.getDeclaredField(name);
253 f.setAccessible(true);
254 f.set(testEnvInstance, Objects.requireNonNull(value));
262 public void displayState()
266 new LogicUIStandaloneGUI(logicModel, new DefaultRenderPreferences()).run();
267 logicModel.setRedrawHandler(null);
275 public enum DebugState
277 NO_DEBUG, DEBUG_AT_PERFORMANCE_COST;