Improved usage of CoreModelParametersBuilder
[Mograsim.git] / tests / net.mograsim.logic.model.am2900.tests / src / net / mograsim / logic / model / am2900 / util / TestEnvironmentHelper.java
1 package net.mograsim.logic.model.am2900.util;
2
3 import static org.junit.jupiter.api.Assertions.fail;
4
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;
12 import java.util.Set;
13 import java.util.TreeSet;
14
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;
36
37 public class TestEnvironmentHelper
38 {
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();
44
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<>();
51
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;
58
59         public TestEnvironmentHelper(TestableCircuit testEnv, String modelId)
60         {
61                 this.testEnvInstance = testEnv;
62                 this.modelId = modelId;
63                 this.testEnvClass = testEnvInstance.getClass();
64                 for (Field f : testEnvClass.getDeclaredFields())
65                 {
66                         if (ModelComponent.class.isAssignableFrom(f.getType()))
67                         {
68                                 componentField = f;
69                                 componentField.setAccessible(true);
70                         } else if (Timeline.class.isAssignableFrom(f.getType()))
71                         {
72                                 f.setAccessible(true);
73                                 timelineField = Optional.of(f);
74                         }
75                 }
76                 if (componentField == null)
77                         throw new IllegalStateException("No component or timeline field found!");
78         }
79
80         public void setup(DebugState debug)
81         {
82                 this.debug = debug;
83                 // Create logic model
84                 logicModel = new LogicModelModifiable();
85                 modellingTool = ModellingTool.createFor(logicModel);
86                 Am2900Loader.setup();
87                 component = IndirectModelComponentCreator.createComponent(logicModel, modelId);
88                 setField(componentField, component);
89
90                 component.getPins().values().forEach(this::extendModelPin);
91
92                 // Create core model
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));
99
100                 // Bind switches/displays to this test class
101                 component.getPins().values().forEach(this::bindModelPin);
102
103                 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
104                 {
105                         setupDebugging();
106                 }
107                 timeline.addEventAddedListener(te -> eventCounter++);
108         }
109
110         private void extendModelPin(Pin p)
111         {
112                 String javaIdentId = idToJavaIdentifier(p.name);
113                 try
114                 {
115                         Field f = testEnvClass.getDeclaredField(javaIdentId);
116                         Class<?> type = f.getType();
117                         if (CoreManualSwitch.class.isAssignableFrom(type))
118                         {
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))
123                         {
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))
128                         {
129                                 SwitchWithDisplay swd = new SwitchWithDisplay(logicModel, p);
130                                 setField(f, swd);
131                         } else
132                         {
133                                 fail("unkown field type " + type);
134                         }
135                 }
136                 catch (NoSuchFieldException | SecurityException e)
137                 {
138                         fail(e);
139                 }
140         }
141
142         private void bindModelPin(Pin p)
143         {
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());
149         }
150
151         private void setupDebugging()
152         {
153                 // Debug code
154                 HashSet<ModelWire> wiresIncludingSubmodels = new HashSet<>();
155                 Queue<LogicModel> modelsToIterate = new LinkedList<>();
156                 modelsToIterate.add(logicModel);
157                 while (modelsToIterate.size() > 0)
158                 {
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);
164                 }
165                 System.out.println(wiresIncludingSubmodels.size());
166                 logicModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w ->
167                 {
168                         if (debugWires)
169                         {
170                                 wireDebugChangeSet.add(w.toString());
171                         }
172                 }));
173         }
174
175         public Result run()
176         {
177                 // Normal execution until completion or eventLimit
178                 int eventLimit = debugEventThreshold;
179                 eventCounter = 0;
180                 debugWires = false;
181                 while (eventCounter < eventLimit)
182                 {
183                         timeline.executeNext();
184                         if (!timeline.hasNext())
185                                 return Result.SUCCESS;
186                 }
187
188                 // Start debugging if event limit is reached (if debug is active)
189                 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
190                         return debugThisRun();
191
192                 return Result.OUT_OF_TIME;
193         }
194
195         private Result debugThisRun()
196         {
197                 int eventLimit = debugEventThreshold;
198                 debugWires = true;
199                 wireDebugChangeSet = new TreeSet<>();
200                 Set<String> oldChangeSet;
201                 // observe wire changes to detect, if we are really stuck in an endless loop
202                 do
203                 {
204                         eventLimit += debugEventCount;
205                         oldChangeSet = wireDebugChangeSet;
206                         wireDebugChangeSet = new TreeSet<>();
207                         while (eventCounter < eventLimit)
208                         {
209                                 timeline.executeNext();
210                                 if (!timeline.hasNext())
211                                 {
212                                         // no endless loop, but more events needed than expected
213                                         System.out.println("run() took longer than expected: " + eventCounter);
214                                         return Result.SUCCESS;
215                                 }
216                         }
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;
223         }
224
225         private static String idToJavaIdentifier(String s)
226         {
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();
233         }
234
235         private <S> void setField(Field f, S value)
236         {
237                 try
238                 {
239                         f.setAccessible(true);
240                         f.set(testEnvInstance, Objects.requireNonNull(value));
241                 }
242                 catch (Exception e)
243                 {
244                         fail(e);
245                 }
246         }
247
248         private <S> void setField(String name, S value)
249         {
250                 try
251                 {
252                         Field f = testEnvClass.getDeclaredField(name);
253                         f.setAccessible(true);
254                         f.set(testEnvInstance, Objects.requireNonNull(value));
255                 }
256                 catch (Exception e)
257                 {
258                         fail(e);
259                 }
260         }
261
262         public void displayState()
263         {
264                 try
265                 {
266                         new LogicUIStandaloneGUI(logicModel, new DefaultRenderPreferences()).run();
267                         logicModel.setRedrawHandler(null);
268                 }
269                 catch (Exception e)
270                 {
271                         e.printStackTrace();
272                 }
273         }
274
275         public enum DebugState
276         {
277                 NO_DEBUG, DEBUG_AT_PERFORMANCE_COST;
278         }
279 }