1 package net.mograsim.plugin.launch;
3 import static org.eclipse.core.resources.IResourceDelta.CHANGED;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.util.ArrayList;
9 import java.util.Optional;
10 import java.util.concurrent.atomic.AtomicBoolean;
11 import java.util.function.Consumer;
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;
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;
47 public class MachineDebugTarget extends PlatformObject implements IDebugTarget, IMemoryBlockRetrievalExtension
49 private final static boolean USE_PSEUDO_THREAD = true;
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;
58 private boolean running;
60 private final List<Consumer<Double>> executionSpeedListeners;
62 private final IResourceChangeListener resChangedListener;
64 public MachineDebugTarget(ILaunch launch, IFile mpmFile, Optional<IFile> memFile, MachineDefinition machineDefinition)
68 this.machine = machineDefinition.createNew();
69 this.exec = new LogicExecuter(machine.getTimeline());
71 this.executionSpeedListeners = new ArrayList<>();
72 this.mpmFile = mpmFile;
73 this.memFile = memFile;
75 assignMicroInstructionMemory();
78 exec.startLiveExecution();
81 getLaunch().addDebugTarget(this);
84 this.resChangedListener = this::resourceChanged;
85 ResourcesPlugin.getWorkspace().addResourceChangeListener(resChangedListener, IResourceChangeEvent.POST_CHANGE);
87 // create after creating ourself
88 this.thread = USE_PSEUDO_THREAD ? new MachineThread(this) : null;
91 public Machine getMachine()
97 public String getName() throws DebugException
99 return "Mograsim machine \"" + machine.getDefinition().getId() + '"';
103 public String getModelIdentifier()
105 return MograsimActivator.PLUGIN_ID;
109 public IDebugTarget getDebugTarget()
115 public ILaunch getLaunch()
120 public double getExecutionSpeed()
122 return exec.getSpeedFactor();
125 public void setExecutionSpeed(double speed)
127 if (getExecutionSpeed() != speed)
129 exec.setSpeedFactor(speed);
130 callExecutionSpeedListener(speed);
135 public boolean isSuspended()
137 return exec.isPaused();
141 public boolean canSuspend()
143 return !isTerminated() && !isSuspended();
147 public void suspend() throws DebugException
150 throwDebugException("Can't suspend a terminated MachineProcess");
152 throwDebugException("Can't suspend a suspended MachineProcess");
154 exec.pauseLiveExecution();
155 fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
159 public boolean canResume()
161 return !isTerminated() && isSuspended();
165 public void resume() throws DebugException
168 throwDebugException("Can't resume a terminated MachineProcess");
170 throwDebugException("Can't resume a non-suspended MachineProcess");
172 exec.unpauseLiveExecution();
173 fireResumeEvent(DebugEvent.CLIENT_REQUEST);
177 public boolean isTerminated()
183 public boolean canTerminate()
185 return !isTerminated();
189 public void terminate() throws DebugException
194 ResourcesPlugin.getWorkspace().removeResourceChangeListener(resChangedListener);
195 exec.stopLiveExecution();
197 fireTerminateEvent();
201 public boolean supportsBreakpoint(IBreakpoint breakpoint)
207 public void breakpointAdded(IBreakpoint breakpoint)
209 // ignore; we don't support breakpoints
213 public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta)
215 // ignore; we don't support breakpoints
219 public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta)
221 // ignore; we don't support breakpoints
225 public boolean isDisconnected()
231 public boolean canDisconnect()
237 public void disconnect() throws DebugException
239 throw new DebugException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.NOT_SUPPORTED,
240 "Can't disconnect from a MachineDebugTarget", null));
244 public boolean supportsStorageRetrieval()
249 @SuppressWarnings("deprecation") // TODO can we throw a DebugException instead?
251 public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException
253 return new MainMemoryBlock(this, startAddress, length);
257 public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException
259 return new MainMemoryBlockExtension(this, expression, context);
263 public IProcess getProcess()
269 public boolean hasThreads() throws DebugException
271 return USE_PSEUDO_THREAD;
275 public IThread[] getThreads() throws DebugException
277 return thread == null ? new IThread[0] : new IThread[] { thread };
280 public void addExecutionSpeedListener(Consumer<Double> executionSpeedListener)
282 executionSpeedListeners.add(executionSpeedListener);
285 public void removeExecutionSpeedListener(Consumer<Double> executionSpeedListener)
287 executionSpeedListeners.remove(executionSpeedListener);
290 private void callExecutionSpeedListener(double executionSpeed)
292 executionSpeedListeners.forEach(l -> l.accept(executionSpeed));
295 @SuppressWarnings("unchecked")
297 public <T> T getAdapter(Class<T> adapter)
299 if (adapter == IDebugElement.class)
302 // leave this here; maybe we implement IStepFilters someday
303 if (adapter == IStepFilters.class)
304 if (this instanceof IStepFilters)
305 return (T) getDebugTarget();
307 if (adapter == IDebugTarget.class)
308 return (T) getDebugTarget();
310 if (adapter == ILaunch.class)
311 return (T) getLaunch();
314 if (adapter == ILaunchConfiguration.class)
315 return (T) getLaunch().getLaunchConfiguration();
317 return super.getAdapter(adapter);
321 * Fires a creation event for this debug element.
323 private void fireCreationEvent()
325 fireEvent(new DebugEvent(this, DebugEvent.CREATE));
329 * Fires a resume for this debug element with the specified detail code.
331 * @param detail detail code for the resume event, such as <code>DebugEvent.STEP_OVER</code>
333 private void fireResumeEvent(int detail)
335 fireEvent(new DebugEvent(this, DebugEvent.RESUME, detail));
339 * Fires a suspend event for this debug element with the specified detail code.
341 * @param detail detail code for the suspend event, such as <code>DebugEvent.BREAKPOINT</code>
343 private void fireSuspendEvent(int detail)
345 fireEvent(new DebugEvent(this, DebugEvent.SUSPEND, detail));
349 * Fires a terminate event for this debug element.
351 private void fireTerminateEvent()
353 fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
357 * Fires a debug event.
359 * @param event debug event to fire
361 private static void fireEvent(DebugEvent event)
363 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
366 private static void throwDebugException(String message) throws DebugException
368 throw new DebugException(
369 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));
372 private void resourceChanged(IResourceChangeEvent event)
374 IResourceDelta mpmDelta;
375 if (event.getType() == IResourceChangeEvent.POST_CHANGE && (mpmDelta = event.getDelta().findMember(mpmFile.getFullPath())) != null
376 && (mpmDelta.getKind() & CHANGED) == CHANGED && mpmFile.exists())
378 AtomicBoolean doHotReplace = new AtomicBoolean();
379 PlatformUI.getWorkbench().getDisplay().syncExec(() ->
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?",
384 doHotReplace.set(true);
386 if (doHotReplace.get())
390 assignMicroInstructionMemory();
392 catch (CoreException e)
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()));
403 private void assignMicroInstructionMemory() throws CoreException
405 try (InputStream mpmStream = mpmFile.getContents())
407 machine.getMicroInstructionMemory().bind(
408 MicroInstructionMemoryParser.parseMemory(machine.getDefinition().getMicroInstructionMemoryDefinition(), mpmStream));
410 catch (IOException e)
412 throw new CoreException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading MPM file", e));
416 private void assignMainMemory() throws CoreException
418 if (memFile.isPresent())
420 try (InputStream initialRAMStream = memFile.get().getContents())
422 machine.getMainMemory()
423 .bind(MainMemoryParser.parseMemory(machine.getDefinition().getMainMemoryDefinition(), initialRAMStream));
425 catch (IOException e)
427 throw new CoreException(
428 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading initial RAM file", e));
433 public IFile getMPMFile()
438 public Optional<IFile> getMEMFile()