/// <summary> /// Starts the machine in a background thread. /// </summary> /// <param name="options">Options to start the machine with.</param> /// <remarks> /// Reports completion when the machine starts executing its cycles. The machine can /// go into Paused or Stopped state, if the execution options allow, for example, /// when it runs to a predefined breakpoint. /// </remarks> public void Start(ExecuteCycleOptions options) { if (VmState == VmState.Running) { return; } // --- Prepare the machine to run IsFirstStart = VmState == VmState.None || VmState == VmState.Stopped; SpectrumVm.DebugInfoProvider?.PrepareBreakpoints(); if (IsFirstStart) { SpectrumVm.Reset(); SpectrumVm.Cpu.StackDebugSupport.ClearStepOutStack(); SpectrumVm.DebugInfoProvider?.ResetHitCounts(); } // --- Dispose the previous cancellation token, and create a new one CancellationTokenSource?.Dispose(); CancellationTokenSource = new CancellationTokenSource(); // --- Set up the task that runs the machine CompletionTask = new Task(async() => { Cancelled = false; ExecutionCycleResult = false; try { ExecutionCycleResult = SpectrumVm.ExecuteCycle(CancellationTokenSource.Token, options); } catch (TaskCanceledException) { Cancelled = true; } catch (Exception ex) { ExecutionCycleException = ex; } // --- Conclude the execution task await ExecuteOnMainThread(() => { MoveToState(VmState == VmState.Stopping || VmState == VmState.Stopped || ExecutionCycleException != null ? VmState.Stopped : VmState.Paused); if (ExecutionCycleException != null) { VmStoppedWithException?.Invoke(this, EventArgs.Empty); } }); }); MoveToState(VmState.Running); CompletionTask.Start(); }
/// <summary> /// Starts the machine in a background thread with the specified options. /// </summary> /// <remarks> /// Reports completion when the machine starts executing its cycles. The machine can /// go into Paused or Stopped state, if the execution options allow, for example, /// when it runs to a predefined breakpoint. /// </remarks> public void StartWithOptions(ExecuteCycleOptions options) { if (MachineState == VmState.Running) { return; } // --- Prepare the machine to run IsFirstStart = MachineState == VmState.None || MachineState == VmState.Stopped; SpectrumVm.DebugInfoProvider?.PrepareBreakpoints(); MachineState = VmState.Starting; if (IsFirstStart) { SpectrumVm.Reset(); SpectrumVm.Cpu.StackDebugSupport.Reset(); SpectrumVm.DebugInfoProvider?.ResetHitCounts(); CpuFrameCount = 0; RenderFrameCount = 0; } // --- Dispose the previous cancellation token, and create a new one _cancellationTokenSource?.Dispose(); _cancellationTokenSource = new CancellationTokenSource(); // --- Set up the task that runs the machine MachineState = VmState.Running; try { _completionTask = StartAndRun(_cancellationTokenSource.Token, options); _completionTask.GetAwaiter().OnCompleted(async() => { await WaitForPause(); }); } catch (TaskCanceledException) { ExecutionCompletionReason = ExecutionCompletionReason.Cancelled; } catch (Exception ex) { ExceptionRaised?.Invoke(this, new VmExceptionArgs(ex)); } }
/// <summary> /// Builds the machine that can be started /// </summary> protected virtual void BuildMachine() { if (SpectrumVm == null) { MoveToState(VmState.BuildingMachine); if (StartupConfiguration == null) { throw new InvalidOperationException("You must provide a startup configuration for " + "the virtual machine, it cannot be null"); } // --- Create the machine on first start SpectrumVm = new Spectrum48(StartupConfiguration.DeviceData, this); SpectrumVm.ScreenDevice.FrameCompleted += (s, e) => VmScreenRefreshed?.Invoke(s, new VmScreenRefreshedEventArgs(SpectrumVm.ScreenDevice.GetPixelBuffer())); } // --- We either provider out DebugInfoProvider, or use // --- the default one if (StartupConfiguration.DebugInfoProvider == null) { StartupConfiguration.DebugInfoProvider = SpectrumVm.DebugInfoProvider; } else { SpectrumVm.DebugInfoProvider = StartupConfiguration.DebugInfoProvider; } // --- Set up stack debug support if (StartupConfiguration.StackDebugSupport != null) { SpectrumVm.Cpu.StackDebugSupport = StartupConfiguration.StackDebugSupport; StartupConfiguration.StackDebugSupport.Reset(); } // --- At this point we have a Spectrum VM. // --- Let's reset it SpectrumVm.Reset(); }
/// <summary> /// Starts the virtual machine with the provided options /// </summary> /// <param name="options">The execution cycle options to start with</param> public void StartVm(ExecuteCycleOptions options) { if (VmState == VmState.Running) { return; } IsFirstStart = VmState == VmState.None || VmState == VmState.BuildingMachine || VmState == VmState.Stopped; if (IsFirstStart) { EnsureMachine(); } // --- We allow this event to execute something on the main thread // --- right before the execution cycle starts MoveToState(VmState.BeforeRun); // --- Dispose the previous cancellation token, and create a new one CancellationTokenSource?.Dispose(); CancellationTokenSource = new CancellationTokenSource(); // --- We use the completion source to sign that the VM's execution cycle is done _vmStarterCompletionSource = new TaskCompletionSource <bool>(); _executionCompletionSource = new TaskCompletionSource <bool>(); // --- Get the exception of the execution cycle Exception exDuringRun = null; // --- Allow the controller to save its current scheduler context SaveMainContext(); SpectrumVm.DebugInfoProvider?.PrepareBreakpoints(); Task.Factory.StartNew(ExecutionAction, CancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current); // --- Execute the VM cycle async void ExecutionAction() { try { SpectrumVm.ExecuteCycle(CancellationTokenSource.Token, options); } catch (TaskCanceledException) { } catch (Exception ex) { exDuringRun = ex; } // --- Forget about the cancellation token CancellationTokenSource?.Dispose(); CancellationTokenSource = null; // --- Go back to the main thread before setting up new state await ExecuteOnMainThread(() => { // --- Calculate next state MoveToState(VmState == VmState.Stopping || exDuringRun != null ? VmState.Stopped : VmState.Paused); // --- Conclude the execution task if (exDuringRun == null) { _executionCompletionSource.SetResult(true); } else { _executionCompletionSource.SetException(exDuringRun); OnVmStoppedWithException(exDuringRun); } }); } }
/// <summary> /// Starts the virtual machine and runs it until the execution cycle is completed for /// a reason. /// </summary> /// <param name="cancellationToken">Cancellation token</param> /// <param name="options">Virtual machine execution options</param> /// <returns>The reason why the execution completed.</returns> private async Task <ExecutionCompletionReason> StartAndRun(CancellationToken cancellationToken, ExecuteCycleOptions options) { LastExecutionContentionValue = ContentionAccumulated; var lastRunStart = _clockProvider.GetCounter(); var lastRenderFrameStart = lastRunStart; var frameCount = 0; var completed = false; while (!completed) { // --- Execute a single CPU Frame var lastCpuFrameStart = _clockProvider.GetCounter(); var cycleCompleted = SpectrumVm.ExecuteCycle(cancellationToken, options, true); LastCpuFrameTicks = _clockProvider.GetCounter() - lastCpuFrameStart; if (!cycleCompleted) { return(ExecutionCompletionReason.Cancelled); } // --- Check for emulated keys CpuFrameCount++; var hasEmulatedKey = SpectrumVm.KeyboardProvider?.EmulateKeyStroke(); if (hasEmulatedKey != true) { // --- Keyboard scan var keyStatusArgs = new KeyStatusEventArgs(); KeyScanning?.Invoke(this, keyStatusArgs); foreach (var keyStatus in keyStatusArgs.KeyStatusList) { SpectrumVm.KeyboardDevice.SetStatus(keyStatus); } } // --- Do additional tasks when CPU frame completed var cancelArgs = new CancelEventArgs(false); CpuFrameCompleted?.Invoke(this, cancelArgs); if (cancelArgs.Cancel) { return(ExecutionCompletionReason.Cancelled); } switch (SpectrumVm.ExecutionCompletionReason) { case ExecutionCompletionReason.TerminationPointReached: case ExecutionCompletionReason.BreakpointReached: case ExecutionCompletionReason.Halted: case ExecutionCompletionReason.Exception: completed = true; break; case ExecutionCompletionReason.RenderFrameCompleted: var lastFrameEnd = _clockProvider.GetCounter(); LastRenderFrameTicks = lastFrameEnd - lastRenderFrameStart; lastRenderFrameStart = lastFrameEnd; completed = options.EmulationMode == EmulationMode.UntilRenderFrameEnds; frameCount++; RenderFrameCount++; // --- Do additional task when render frame completed var renderFrameArgs = new RenderFrameEventArgs(SpectrumVm.ScreenDevice.GetPixelBuffer()); RenderFrameCompleted?.Invoke(this, renderFrameArgs); if (renderFrameArgs.Cancel) { return(ExecutionCompletionReason.Cancelled); } // --- Wait for the next render frame, unless completed if (!completed) { var waitInTicks = lastRunStart + frameCount * _physicalFrameClockCount - _clockProvider.GetCounter() - _physicalFrameClockCount * 0.2; var waitInMs = 1000.0 * waitInTicks / _clockProvider.GetFrequency(); if (waitInMs > 0) { await Task.Delay((int)waitInMs, cancellationToken); if (cancellationToken.IsCancellationRequested) { return(ExecutionCompletionReason.Cancelled); } } else { await Task.Delay(1, cancellationToken); } } break; } } // --- Done, pass back the reason of completing the run return(SpectrumVm.ExecutionCompletionReason); }