Using Builder pattern for CoreModelParameters now
[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.CoreModelParametersBuilder;
31 import net.mograsim.logic.model.modeladapter.LogicCoreAdapter;
32 import net.mograsim.logic.model.preferences.DefaultRenderPreferences;
33 import net.mograsim.logic.model.serializing.IndirectModelComponentCreator;
34 import net.mograsim.logic.model.util.ModellingTool;
35
36 public class TestEnvironmentHelper
37 {
38         private final TestableCircuit testEnvInstance;
39         private final Class<?> testEnvClass;
40         private final String modelId;
41         private Field componentField;
42         private Optional<Field> timelineField = Optional.empty();
43
44         private ModelComponent component;
45         private Timeline timeline;
46         private LogicModelModifiable logicModel;
47         private ModellingTool modellingTool;
48         private HashMap<String, ModelManualSwitch> idSwitchMap = new HashMap<>();
49         private HashMap<String, ModelBitDisplay> idDisplayMap = new HashMap<>();
50
51         private DebugState debug = DebugState.NO_DEBUG;
52         private Set<String> wireDebugChangeSet;
53         private boolean debugWires = false;
54         public int debugEventThreshold = 10_000;
55         public int debugEventCount = 500;
56         private int eventCounter;
57
58         public TestEnvironmentHelper(TestableCircuit testEnv, String modelId)
59         {
60                 this.testEnvInstance = testEnv;
61                 this.modelId = modelId;
62                 this.testEnvClass = testEnvInstance.getClass();
63                 for (Field f : testEnvClass.getDeclaredFields())
64                 {
65                         if (ModelComponent.class.isAssignableFrom(f.getType()))
66                         {
67                                 componentField = f;
68                                 componentField.setAccessible(true);
69                         } else if (Timeline.class.isAssignableFrom(f.getType()))
70                         {
71                                 f.setAccessible(true);
72                                 timelineField = Optional.of(f);
73                         }
74                 }
75                 if (componentField == null)
76                         throw new IllegalStateException("No component or timeline field found!");
77         }
78
79         public void setup(DebugState debug)
80         {
81                 this.debug = debug;
82                 // Create logic model
83                 logicModel = new LogicModelModifiable();
84                 modellingTool = ModellingTool.createFor(logicModel);
85                 Am2900Loader.setup();
86                 component = IndirectModelComponentCreator.createComponent(logicModel, modelId);
87                 setField(componentField, component);
88
89                 component.getPins().values().forEach(this::extendModelPin);
90
91                 // Create core model
92                 CoreModelParametersBuilder paramsBuilder = new CoreModelParametersBuilder();
93                 paramsBuilder.gateProcessTime = 50;
94                 paramsBuilder.hardcodedComponentProcessTime = paramsBuilder.gateProcessTime * 5;
95                 paramsBuilder.wireTravelTime = 10;
96                 timeline = LogicCoreAdapter.convert(logicModel, paramsBuilder.build());
97                 timelineField.ifPresent(f -> setField(f, timeline));
98
99                 // Bind switches/displays to this test class
100                 component.getPins().values().forEach(this::bindModelPin);
101
102                 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
103                 {
104                         setupDebugging();
105                 }
106                 timeline.addEventAddedListener(te -> eventCounter++);
107         }
108
109         private void extendModelPin(Pin p)
110         {
111                 String javaIdentId = idToJavaIdentifier(p.name);
112                 try
113                 {
114                         Field f = testEnvClass.getDeclaredField(javaIdentId);
115                         Class<?> type = f.getType();
116                         if (CoreManualSwitch.class.isAssignableFrom(type))
117                         {
118                                 ModelManualSwitch gms = new ModelManualSwitch(logicModel, p.logicWidth);
119                                 modellingTool.connect(p, gms.getOutputPin());
120                                 idSwitchMap.put(p.name, gms);
121                         } else if (CoreBitDisplay.class.isAssignableFrom(type))
122                         {
123                                 ModelBitDisplay gbd = new ModelBitDisplay(logicModel, p.logicWidth);
124                                 modellingTool.connect(p, gbd.getInputPin());
125                                 idDisplayMap.put(p.name, gbd);
126                         } else if (SwitchWithDisplay.class.isAssignableFrom(type))
127                         {
128                                 SwitchWithDisplay swd = new SwitchWithDisplay(logicModel, p);
129                                 setField(f, swd);
130                         } else
131                         {
132                                 fail("unkown field type " + type);
133                         }
134                 }
135                 catch (NoSuchFieldException | SecurityException e)
136                 {
137                         fail(e);
138                 }
139         }
140
141         private void bindModelPin(Pin p)
142         {
143                 String javaIdentId = idToJavaIdentifier(p.name);
144                 if (idDisplayMap.containsKey(p.name))
145                         setField(javaIdentId, idDisplayMap.get(p.name).getBitDisplay());
146                 if (idSwitchMap.containsKey(p.name))
147                         setField(javaIdentId, idSwitchMap.get(p.name).getManualSwitch());
148         }
149
150         private void setupDebugging()
151         {
152                 // Debug code
153                 HashSet<ModelWire> wiresIncludingSubmodels = new HashSet<>();
154                 Queue<LogicModel> modelsToIterate = new LinkedList<>();
155                 modelsToIterate.add(logicModel);
156                 while (modelsToIterate.size() > 0)
157                 {
158                         LogicModel model = modelsToIterate.poll();
159                         wiresIncludingSubmodels.addAll(model.getWiresByName().values());
160                         for (ModelComponent comp : model.getComponentsByName().values())
161                                 if (comp instanceof SubmodelComponent)
162                                         modelsToIterate.offer(((SubmodelComponent) comp).submodel);
163                 }
164                 System.out.println(wiresIncludingSubmodels.size());
165                 logicModel.setRedrawHandler(() -> wiresIncludingSubmodels.forEach(w ->
166                 {
167                         if (debugWires)
168                         {
169                                 wireDebugChangeSet.add(w.toString());
170                         }
171                 }));
172         }
173
174         public Result run()
175         {
176                 // Normal execution until completion or eventLimit
177                 int eventLimit = debugEventThreshold;
178                 eventCounter = 0;
179                 debugWires = false;
180                 while (eventCounter < eventLimit)
181                 {
182                         timeline.executeNext();
183                         if (!timeline.hasNext())
184                                 return Result.SUCCESS;
185                 }
186
187                 // Start debugging if event limit is reached (if debug is active)
188                 if (debug == DebugState.DEBUG_AT_PERFORMANCE_COST)
189                         return debugThisRun();
190
191                 return Result.OUT_OF_TIME;
192         }
193
194         private Result debugThisRun()
195         {
196                 int eventLimit = debugEventThreshold;
197                 debugWires = true;
198                 wireDebugChangeSet = new TreeSet<>();
199                 Set<String> oldChangeSet;
200                 // observe wire changes to detect, if we are really stuck in an endless loop
201                 do
202                 {
203                         eventLimit += debugEventCount;
204                         oldChangeSet = wireDebugChangeSet;
205                         wireDebugChangeSet = new TreeSet<>();
206                         while (eventCounter < eventLimit)
207                         {
208                                 timeline.executeNext();
209                                 if (!timeline.hasNext())
210                                 {
211                                         // no endless loop, but more events needed than expected
212                                         System.out.println("run() took longer than expected: " + eventCounter);
213                                         return Result.SUCCESS;
214                                 }
215                         }
216                 } while (!oldChangeSet.equals(wireDebugChangeSet));
217                 // if stuck, abort execution and print wires
218                 System.err.print("Problematic Wire updates:");
219                 wireDebugChangeSet.forEach(System.out::println);
220                 System.err.println("run() failed: " + eventCounter);
221                 return Result.OUT_OF_TIME;
222         }
223
224         private static String idToJavaIdentifier(String s)
225         {
226                 StringBuilder sb = new StringBuilder(s.length());
227                 char c = s.charAt(0);
228                 sb.append(Character.isJavaIdentifierStart(c) ? c : '_');
229                 for (int i = 1; i < s.length(); i++)
230                         sb.append(Character.isJavaIdentifierPart(c = s.charAt(i)) ? c : '_');
231                 return sb.toString();
232         }
233
234         private <S> void setField(Field f, S value)
235         {
236                 try
237                 {
238                         f.setAccessible(true);
239                         f.set(testEnvInstance, Objects.requireNonNull(value));
240                 }
241                 catch (Exception e)
242                 {
243                         fail(e);
244                 }
245         }
246
247         private <S> void setField(String name, S value)
248         {
249                 try
250                 {
251                         Field f = testEnvClass.getDeclaredField(name);
252                         f.setAccessible(true);
253                         f.set(testEnvInstance, Objects.requireNonNull(value));
254                 }
255                 catch (Exception e)
256                 {
257                         fail(e);
258                 }
259         }
260
261         public void displayState()
262         {
263                 try
264                 {
265                         new LogicUIStandaloneGUI(logicModel, new DefaultRenderPreferences()).run();
266                         logicModel.setRedrawHandler(null);
267                 }
268                 catch (Exception e)
269                 {
270                         e.printStackTrace();
271                 }
272         }
273
274         public enum DebugState
275         {
276                 NO_DEBUG, DEBUG_AT_PERFORMANCE_COST;
277         }
278 }