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