ca7480434e927dfb3407954395f14728f9c6b0eb
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / launch / MachineDebugTarget.java
1 package net.mograsim.plugin.launch;
2
3 import static org.eclipse.core.resources.IResourceDelta.CHANGED;
4
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.util.ArrayList;
8 import java.util.List;
9 import java.util.Optional;
10 import java.util.concurrent.atomic.AtomicBoolean;
11 import java.util.function.Consumer;
12
13 import org.eclipse.core.resources.IFile;
14 import org.eclipse.core.resources.IMarkerDelta;
15 import org.eclipse.core.resources.IResourceChangeEvent;
16 import org.eclipse.core.resources.IResourceChangeListener;
17 import org.eclipse.core.resources.IResourceDelta;
18 import org.eclipse.core.resources.ResourcesPlugin;
19 import org.eclipse.core.runtime.CoreException;
20 import org.eclipse.core.runtime.IStatus;
21 import org.eclipse.core.runtime.PlatformObject;
22 import org.eclipse.core.runtime.Status;
23 import org.eclipse.debug.core.DebugEvent;
24 import org.eclipse.debug.core.DebugException;
25 import org.eclipse.debug.core.DebugPlugin;
26 import org.eclipse.debug.core.ILaunch;
27 import org.eclipse.debug.core.ILaunchConfiguration;
28 import org.eclipse.debug.core.model.IBreakpoint;
29 import org.eclipse.debug.core.model.IDebugElement;
30 import org.eclipse.debug.core.model.IDebugTarget;
31 import org.eclipse.debug.core.model.IMemoryBlock;
32 import org.eclipse.debug.core.model.IMemoryBlockExtension;
33 import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
34 import org.eclipse.debug.core.model.IProcess;
35 import org.eclipse.debug.core.model.IStepFilters;
36 import org.eclipse.debug.core.model.IThread;
37 import org.eclipse.jface.dialogs.MessageDialog;
38 import org.eclipse.ui.PlatformUI;
39
40 import net.mograsim.logic.model.LogicExecuter;
41 import net.mograsim.machine.Machine;
42 import net.mograsim.machine.MachineDefinition;
43 import net.mograsim.machine.mi.MicroInstructionMemoryParser;
44 import net.mograsim.machine.standard.memory.MainMemoryParser;
45 import net.mograsim.plugin.MograsimActivator;
46
47 public class MachineDebugTarget extends PlatformObject implements IDebugTarget, IMemoryBlockRetrievalExtension
48 {
49         private final static boolean USE_PSEUDO_THREAD = true;
50
51         private final ILaunch launch;
52         private final Machine machine;
53         private final LogicExecuter exec;
54         private final MachineThread thread;
55         private final IFile mpmFile;
56         private final Optional<IFile> memFile;
57
58         private boolean running;
59
60         private final List<Consumer<Double>> executionSpeedListeners;
61
62         private final IResourceChangeListener resChangedListener;
63
64         public MachineDebugTarget(ILaunch launch, IFile mpmFile, Optional<IFile> memFile, MachineDefinition machineDefinition)
65                         throws CoreException
66         {
67                 this.launch = launch;
68                 this.machine = machineDefinition.createNew();
69                 this.exec = new LogicExecuter(machine.getTimeline());
70
71                 this.executionSpeedListeners = new ArrayList<>();
72                 this.mpmFile = mpmFile;
73                 this.memFile = memFile;
74
75                 assignMicroInstructionMemory();
76                 assignMainMemory();
77
78                 exec.startLiveExecution();
79                 running = true;
80
81                 getLaunch().addDebugTarget(this);
82                 fireCreationEvent();
83
84                 this.resChangedListener = this::resourceChanged;
85                 ResourcesPlugin.getWorkspace().addResourceChangeListener(resChangedListener, IResourceChangeEvent.POST_CHANGE);
86
87                 // create after creating ourself
88                 this.thread = USE_PSEUDO_THREAD ? new MachineThread(this) : null;
89         }
90
91         public Machine getMachine()
92         {
93                 return machine;
94         }
95
96         @Override
97         public String getName() throws DebugException
98         {
99                 return "Mograsim machine \"" + machine.getDefinition().getId() + '"';
100         }
101
102         @Override
103         public String getModelIdentifier()
104         {
105                 return MograsimActivator.PLUGIN_ID;
106         }
107
108         @Override
109         public IDebugTarget getDebugTarget()
110         {
111                 return this;
112         }
113
114         @Override
115         public ILaunch getLaunch()
116         {
117                 return launch;
118         }
119
120         public double getExecutionSpeed()
121         {
122                 return exec.getSpeedFactor();
123         }
124
125         public void setExecutionSpeed(double speed)
126         {
127                 if (getExecutionSpeed() != speed)
128                 {
129                         exec.setSpeedFactor(speed);
130                         callExecutionSpeedListener(speed);
131                 }
132         }
133
134         @Override
135         public boolean isSuspended()
136         {
137                 return exec.isPaused();
138         }
139
140         @Override
141         public boolean canSuspend()
142         {
143                 return !isTerminated() && !isSuspended();
144         }
145
146         @Override
147         public void suspend() throws DebugException
148         {
149                 if (isTerminated())
150                         throwDebugException("Can't suspend a terminated MachineProcess");
151                 if (isSuspended())
152                         throwDebugException("Can't suspend a suspended MachineProcess");
153
154                 exec.pauseLiveExecution();
155                 fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
156         }
157
158         @Override
159         public boolean canResume()
160         {
161                 return !isTerminated() && isSuspended();
162         }
163
164         @Override
165         public void resume() throws DebugException
166         {
167                 if (isTerminated())
168                         throwDebugException("Can't resume a terminated MachineProcess");
169                 if (!isSuspended())
170                         throwDebugException("Can't resume a non-suspended MachineProcess");
171
172                 exec.unpauseLiveExecution();
173                 fireResumeEvent(DebugEvent.CLIENT_REQUEST);
174         }
175
176         @Override
177         public boolean isTerminated()
178         {
179                 return !running;
180         }
181
182         @Override
183         public boolean canTerminate()
184         {
185                 return !isTerminated();
186         }
187
188         @Override
189         public void terminate() throws DebugException
190         {
191                 if (isTerminated())
192                         return;
193
194                 ResourcesPlugin.getWorkspace().removeResourceChangeListener(resChangedListener);
195                 exec.stopLiveExecution();
196                 running = false;
197                 fireTerminateEvent();
198         }
199
200         @Override
201         public boolean supportsBreakpoint(IBreakpoint breakpoint)
202         {
203                 return false;
204         }
205
206         @Override
207         public void breakpointAdded(IBreakpoint breakpoint)
208         {
209                 // ignore; we don't support breakpoints
210         }
211
212         @Override
213         public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta)
214         {
215                 // ignore; we don't support breakpoints
216         }
217
218         @Override
219         public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta)
220         {
221                 // ignore; we don't support breakpoints
222         }
223
224         @Override
225         public boolean isDisconnected()
226         {
227                 return false;
228         }
229
230         @Override
231         public boolean canDisconnect()
232         {
233                 return false;
234         }
235
236         @Override
237         public void disconnect() throws DebugException
238         {
239                 throw new DebugException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.NOT_SUPPORTED,
240                                 "Can't disconnect from a MachineDebugTarget", null));
241         }
242
243         @Override
244         public boolean supportsStorageRetrieval()
245         {
246                 return true;
247         }
248
249         @SuppressWarnings("deprecation") // TODO can we throw a DebugException instead?
250         @Override
251         public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException
252         {
253                 return new MainMemoryBlock(this, startAddress, length);
254         }
255
256         @Override
257         public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException
258         {
259                 return new MainMemoryBlockExtension(this, expression, context);
260         }
261
262         @Override
263         public IProcess getProcess()
264         {
265                 return null;
266         }
267
268         @Override
269         public boolean hasThreads() throws DebugException
270         {
271                 return USE_PSEUDO_THREAD;
272         }
273
274         @Override
275         public IThread[] getThreads() throws DebugException
276         {
277                 return thread == null ? new IThread[0] : new IThread[] { thread };
278         }
279
280         public void addExecutionSpeedListener(Consumer<Double> executionSpeedListener)
281         {
282                 executionSpeedListeners.add(executionSpeedListener);
283         }
284
285         public void removeExecutionSpeedListener(Consumer<Double> executionSpeedListener)
286         {
287                 executionSpeedListeners.remove(executionSpeedListener);
288         }
289
290         private void callExecutionSpeedListener(double executionSpeed)
291         {
292                 executionSpeedListeners.forEach(l -> l.accept(executionSpeed));
293         }
294
295         @SuppressWarnings("unchecked")
296         @Override
297         public <T> T getAdapter(Class<T> adapter)
298         {
299                 if (adapter == IDebugElement.class)
300                         return (T) this;
301
302                 // leave this here; maybe we implement IStepFilters someday
303                 if (adapter == IStepFilters.class)
304                         if (this instanceof IStepFilters)
305                                 return (T) getDebugTarget();
306
307                 if (adapter == IDebugTarget.class)
308                         return (T) getDebugTarget();
309
310                 if (adapter == ILaunch.class)
311                         return (T) getLaunch();
312
313                 // CONTEXTLAUNCHING
314                 if (adapter == ILaunchConfiguration.class)
315                         return (T) getLaunch().getLaunchConfiguration();
316
317                 return super.getAdapter(adapter);
318         }
319
320         /**
321          * Fires a creation event for this debug element.
322          */
323         private void fireCreationEvent()
324         {
325                 fireEvent(new DebugEvent(this, DebugEvent.CREATE));
326         }
327
328         /**
329          * Fires a resume for this debug element with the specified detail code.
330          *
331          * @param detail detail code for the resume event, such as <code>DebugEvent.STEP_OVER</code>
332          */
333         private void fireResumeEvent(int detail)
334         {
335                 fireEvent(new DebugEvent(this, DebugEvent.RESUME, detail));
336         }
337
338         /**
339          * Fires a suspend event for this debug element with the specified detail code.
340          *
341          * @param detail detail code for the suspend event, such as <code>DebugEvent.BREAKPOINT</code>
342          */
343         private void fireSuspendEvent(int detail)
344         {
345                 fireEvent(new DebugEvent(this, DebugEvent.SUSPEND, detail));
346         }
347
348         /**
349          * Fires a terminate event for this debug element.
350          */
351         private void fireTerminateEvent()
352         {
353                 fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
354         }
355
356         /**
357          * Fires a debug event.
358          *
359          * @param event debug event to fire
360          */
361         private static void fireEvent(DebugEvent event)
362         {
363                 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
364         }
365
366         private static void throwDebugException(String message) throws DebugException
367         {
368                 throw new DebugException(
369                                 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));
370         }
371
372         private void resourceChanged(IResourceChangeEvent event)
373         {
374                 IResourceDelta mpmDelta;
375                 if (event.getType() == IResourceChangeEvent.POST_CHANGE && (mpmDelta = event.getDelta().findMember(mpmFile.getFullPath())) != null
376                                 && (mpmDelta.getKind() & CHANGED) == CHANGED && mpmFile.exists())
377                 {
378                         AtomicBoolean doHotReplace = new AtomicBoolean();
379                         PlatformUI.getWorkbench().getDisplay().syncExec(() ->
380                         {
381                                 if (MessageDialog.openConfirm(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Hot Replace MPM?",
382                                                 String.format("The MPM %s has been modified on the file system. Replace simulated MPM with modified contents?",
383                                                                 mpmFile.getName())))
384                                         doHotReplace.set(true);
385                         });
386                         if (doHotReplace.get())
387                         {
388                                 try
389                                 {
390                                         assignMicroInstructionMemory();
391                                 }
392                                 catch (CoreException e)
393                                 {
394                                         PlatformUI.getWorkbench().getDisplay()
395                                                         .asyncExec(() -> MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
396                                                                         "Failed Hot Replace!",
397                                                                         "An error occurred trying to read the modified MPM from the file system: " + e.getMessage()));
398                                 }
399                         }
400                 }
401         }
402
403         private void assignMicroInstructionMemory() throws CoreException
404         {
405                 try (InputStream mpmStream = mpmFile.getContents())
406                 {
407                         machine.getMicroInstructionMemory().bind(
408                                         MicroInstructionMemoryParser.parseMemory(machine.getDefinition().getMicroInstructionMemoryDefinition(), mpmStream));
409                 }
410                 catch (IOException e)
411                 {
412                         throw new CoreException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading MPM file", e));
413                 }
414         }
415
416         private void assignMainMemory() throws CoreException
417         {
418                 if (memFile.isPresent())
419                 {
420                         try (InputStream initialRAMStream = memFile.get().getContents())
421                         {
422                                 machine.getMainMemory()
423                                                 .bind(MainMemoryParser.parseMemory(machine.getDefinition().getMainMemoryDefinition(), initialRAMStream));
424                         }
425                         catch (IOException e)
426                         {
427                                 throw new CoreException(
428                                                 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading initial RAM file", e));
429                         }
430                 }
431         }
432
433         public IFile getMPMFile()
434         {
435                 return mpmFile;
436         }
437
438         public Optional<IFile> getMEMFile()
439         {
440                 return memFile;
441         }
442 }