/// <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> /// Checks whether the execution cycle should be stopped for debugging /// </summary> /// <param name="options">Execution options</param> /// <param name="executedInstructionCount"> /// The count of instructions already executed in this cycle /// </param> /// <returns>True, if the execution should be stopped</returns> private bool IsDebugStop(ExecuteCycleOptions options, int executedInstructionCount) { // --- No debug provider, no stop if (DebugInfoProvider == null) { return(false); } // Check if the maskable interrupt routine breakpoints should be skipped if (RunsInMaskableInterrupt) { if (options.SkipInterruptRoutine) { return(false); } } // --- In Step-Into mode we always stop when we're about to // --- execute the next instruction if (options.DebugStepMode == DebugStepMode.StepInto) { return(executedInstructionCount > 0); } // --- In Stop-At-Breakpoint mode we stop only if a predefined // --- breakpoint is reached if (options.DebugStepMode == DebugStepMode.StopAtBreakpoint && DebugInfoProvider.ShouldBreakAtAddress(Cpu.Registers.PC)) { if (executedInstructionCount > 0 || _lastBreakpoint == null || _lastBreakpoint != Cpu.Registers.PC) { // --- If we are paused at a breakpoint, we do not want // --- to pause again and again, unless we step through _lastBreakpoint = Cpu.Registers.PC; return(true); } } // --- We're in Step-Over mode if (options.DebugStepMode == DebugStepMode.StepOver) { if (DebugInfoProvider.ImminentBreakpoint != null) { // --- We also stop, if an imminent breakpoint is reached, and also remove // --- this breakpoint if (DebugInfoProvider.ImminentBreakpoint == Cpu.Registers.PC) { DebugInfoProvider.ImminentBreakpoint = null; return(true); } } else { var imminentJustCreated = false; // --- We check for a CALL-like instruction var length = Cpu.GetCallInstructionLength(); if (length > 0) { // --- Its a CALL-like instraction, create an imminent breakpoint DebugInfoProvider.ImminentBreakpoint = (ushort)(Cpu.Registers.PC + length); imminentJustCreated = true; } // --- We stop, we executed at least one instruction and if there's no imminent // --- breakpoint or we've just created one if (executedInstructionCount > 0 && (DebugInfoProvider.ImminentBreakpoint == null || imminentJustCreated)) { return(true); } } } // --- In any other case, we carry on return(false); }
/// <summary> /// The main execution cycle of the Spectrum VM /// </summary> /// <param name="token">Cancellation token</param> /// <param name="options">Execution options</param> /// <return>True, if the cycle completed; false, if it has been cancelled</return> public bool ExecuteCycle(CancellationToken token, ExecuteCycleOptions options) { ExecuteCycleOptions = options; ExecutionCompletionReason = ExecutionCompletionReason.None; LastExecutionStartTact = Cpu.Tacts; LastExecutionContentionValue = ContentionAccumulated; // --- We use these variables to calculate wait time at the end of the frame var cycleStartTime = Clock.GetCounter(); var cycleStartTact = Cpu.Tacts; var cycleFrameCount = 0; // --- We use this variable to check whether to stop in Debug mode var executedInstructionCount = -1; // --- Loop #1: The main cycle that goes on until cancelled while (!token.IsCancellationRequested) { if (_frameCompleted) { // --- This counter helps us to calculate where we are in the frame after // --- each CPU operation cycle LastFrameStartCpuTick = Cpu.Tacts - Overflow; // --- Notify devices to start a new frame OnNewFrame(); LastRenderedUlaTact = Overflow; _frameCompleted = false; } // --- Loop #2: The physical frame cycle that goes on while CPU and ULA // --- processes everything whithin a physical frame (0.019968 second) while (!_frameCompleted) { // --- Check for leaving maskable interrupt mode if (RunsInMaskableInterrupt) { if (Cpu.Registers.PC == 0x0052) { // --- We leave the maskable interrupt mode when the // --- current instruction completes RunsInMaskableInterrupt = false; } } // --- Check debug mode when a CPU instruction has been entirelly executed if (!Cpu.IsInOpExecution) { // --- Check for cancellation if (token.IsCancellationRequested) { ExecutionCompletionReason = ExecutionCompletionReason.Cancelled; return(false); } // --- The next instruction is about to be executed executedInstructionCount++; // --- Check for timeout if (options.TimeoutTacts > 0 && cycleStartTact + options.TimeoutTacts < Cpu.Tacts) { ExecutionCompletionReason = ExecutionCompletionReason.Timeout; return(false); } // --- Check for reaching the termination point if (options.EmulationMode == EmulationMode.UntilExecutionPoint) { if (options.TerminationPoint < 0x4000) { // --- ROM & address must match if (options.TerminationRom == MemoryDevice.GetSelectedRomIndex() && options.TerminationPoint == Cpu.Registers.PC) { // --- We reached the termination point within ROM ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached; return(true); } } else if (options.TerminationPoint == Cpu.Registers.PC) { // --- We reached the termination point within RAM ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached; return(true); } } // --- Check for entering maskable interrupt mode if (Cpu.MaskableInterruptModeEntered) { RunsInMaskableInterrupt = true; } // --- Check for a debugging stop point if (options.EmulationMode == EmulationMode.Debugger) { if (IsDebugStop(options, executedInstructionCount)) { // --- At this point, the cycle should be stopped because of debugging reasons // --- The screen should be refreshed ScreenDevice.OnFrameCompleted(); ExecutionCompletionReason = ExecutionCompletionReason.BreakpointReached; return(true); } } } // --- Check for interrupt signal generation InterruptDevice.CheckForInterrupt(CurrentFrameTact); // --- Run a single Z80 instruction Cpu.ExecuteCpuCycle(); _lastBreakpoint = null; // --- Run a rendering cycle according to the current CPU tact count var lastTact = CurrentFrameTact; ScreenDevice.RenderScreen(LastRenderedUlaTact + 1, lastTact); LastRenderedUlaTact = lastTact; // --- Exit if the emulation mode specifies so if (options.EmulationMode == EmulationMode.UntilHalt && (Cpu.StateFlags & Z80StateFlags.Halted) != 0) { ExecutionCompletionReason = ExecutionCompletionReason.Halted; return(true); } // --- Notify each CPU-bound device that the current operation has been completed foreach (var device in _cpuBoundDevices) { device.OnCpuOperationCompleted(); } // --- Decide whether this frame has been completed _frameCompleted = !Cpu.IsInOpExecution && CurrentFrameTact >= _frameTacts; } // -- End Loop #2 // --- A physical frame has just been completed. Take care about screen refresh cycleFrameCount++; FrameCount++; // --- Notify devices that the current frame completed OnFrameCompleted(); // --- Exit if the emulation mode specifies so if (options.EmulationMode == EmulationMode.UntilFrameEnds) { ExecutionCompletionReason = ExecutionCompletionReason.FrameCompleted; return(true); } // --- Wait while the frame time ellapses if (!ExecuteCycleOptions.FastVmMode) { var nextFrameCounter = cycleStartTime + cycleFrameCount * PhysicalFrameClockCount; Clock.WaitUntil((long)nextFrameCounter, token); } // --- Start a new frame and carry on Overflow = CurrentFrameTact % _frameTacts; } // --- End Loop #1 // --- The cycle has been interrupted by cancellation ExecutionCompletionReason = ExecutionCompletionReason.Cancelled; return(false); }
/// <summary> /// Checks whether the execution cycle should be stopped for debugging /// </summary> /// <param name="options">Execution options</param> /// <param name="executedInstructionCount"> /// The count of instructions already executed in this cycle /// </param> /// <param name="entryStepOutStackDepth">The initial depth of the Step-Out stack</param> /// <returns>True, if the execution should be stopped</returns> private bool IsDebugStop(ExecuteCycleOptions options, int executedInstructionCount, int entryStepOutStackDepth) { // --- No debug provider, no stop if (DebugInfoProvider == null) { return(false); } switch (options.DebugStepMode) { // --- In Step-Into mode we always stop when we're about to // --- execute the next instruction case DebugStepMode.StepInto: return(executedInstructionCount > 0); // --- In Stop-At-Breakpoint mode we stop only if a predefined // --- breakpoint is reached case DebugStepMode.StopAtBreakpoint when DebugInfoProvider.ShouldBreakAtAddress(Cpu.Registers.PC) && (executedInstructionCount > 0 || _lastBreakpoint == null || _lastBreakpoint != Cpu.Registers.PC): // --- If we are paused at a breakpoint, we do not want // --- to pause again and again, unless we step through _lastBreakpoint = Cpu.Registers.PC; return(true); // --- We're in Step-Over mode case DebugStepMode.StepOver when DebugInfoProvider.ImminentBreakpoint != null: { // --- We also stop, if an imminent breakpoint is reached, and also remove // --- this breakpoint if (DebugInfoProvider.ImminentBreakpoint == Cpu.Registers.PC) { DebugInfoProvider.ImminentBreakpoint = null; return(true); } break; } case DebugStepMode.StepOver: { var imminentJustCreated = false; // --- We check for a CALL-like instruction var length = Cpu.GetCallInstructionLength(); if (length > 0) { // --- Its a CALL-like instruction, create an imminent breakpoint DebugInfoProvider.ImminentBreakpoint = (ushort)(Cpu.Registers.PC + length); imminentJustCreated = true; } // --- We stop, we executed at least one instruction and if there's no imminent // --- breakpoint or we've just created one if (executedInstructionCount > 0 && (DebugInfoProvider.ImminentBreakpoint == null || imminentJustCreated)) { return(true); } break; } // --- We're in Step-Out mode and want to complete the current subroutine call case DebugStepMode.StepOut when Cpu.StackDebugSupport.RetExecuted && executedInstructionCount > 0 && entryStepOutStackDepth == Cpu.StackDebugSupport.StepOutStackDepth + 1: { if (Cpu.Registers.PC != Cpu.StackDebugSupport.StepOutAddress) { Cpu.StackDebugSupport.ClearStepOutStack(); } return(true); } } // --- In any other case, we carry on return(false); }
/// <summary> /// The main execution cycle of the Spectrum VM /// </summary> /// <param name="token">Cancellation token</param> /// <param name="options">Execution options</param> /// <param name="completeOnCpuFrame">The cycle should complete on CPU frame completion</param> /// <return>True, if the cycle completed; false, if it has been cancelled</return> public bool ExecuteCycle(CancellationToken token, ExecuteCycleOptions options, bool completeOnCpuFrame = false) { ExecuteCycleOptions = options; ExecutionCompletionReason = ExecutionCompletionReason.None; LastExecutionStartTact = Cpu.Tacts; // --- We use this variables to check whether to stop in Debug mode var executedInstructionCount = -1; var entryStepOutDepth = Cpu.StackDebugSupport.StepOutStackDepth; // --- Check if we're just start running the next frame if (HasFrameCompleted) { // --- This counter helps us to calculate where we are in the frame after // --- each CPU operation cycle LastFrameStartCpuTick = Cpu.Tacts - Overflow; // --- Notify devices to start a new frame OnNewFrame(); LastRenderedUlaTact = Overflow; HasFrameCompleted = false; } // --- The physical frame cycle that goes on while CPU and ULA // --- processes everything within a physical frame (0.019968 second) while (!HasFrameCompleted) { // --- Check debug mode when a CPU instruction has been entirely executed if (!Cpu.IsInOpExecution) { // --- The next instruction is about to be executed executedInstructionCount++; // --- Check for cancellation if (token.IsCancellationRequested) { ExecutionCompletionReason = ExecutionCompletionReason.Cancelled; return(false); } // --- Check for CPU frame completion if (completeOnCpuFrame && Cpu.Tacts > LastExecutionStartTact + CPU_FRAME) { ExecutionCompletionReason = ExecutionCompletionReason.CpuFrameCompleted; return(true); } // --- Check for several termination modes switch (options.EmulationMode) { // --- Check for reaching the termination point case EmulationMode.UntilExecutionPoint when options.TerminationPoint < 0x4000: { // --- ROM & address must match if (options.TerminationRom == MemoryDevice.GetSelectedRomIndex() && options.TerminationPoint == Cpu.Registers.PC) { // --- We reached the termination point within ROM ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached; return(true); } break; } // --- Check if we reached the termination point within RAM case EmulationMode.UntilExecutionPoint when options.TerminationPoint == Cpu.Registers.PC: ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached; return(true); // --- Check for a debugging stop point case EmulationMode.Debugger when IsDebugStop(options, executedInstructionCount, entryStepOutDepth) && DebugConditionSatisfied(): // --- At this point, the cycle should be stopped because of debugging reasons // --- The screen should be refreshed ExecutionCompletionReason = ExecutionCompletionReason.BreakpointReached; return(true); } } // --- Check for interrupt signal generation InterruptDevice.CheckForInterrupt(CurrentFrameTact); // --- Run a single Z80 instruction Cpu.ExecuteCpuCycle(); _lastBreakpoint = null; // --- Run a rendering cycle according to the current CPU tact count var lastTact = CurrentFrameTact; ScreenDevice.RenderScreen(LastRenderedUlaTact + 1, lastTact); LastRenderedUlaTact = lastTact; // --- Exit if the CPU is HALTed and the emulation mode specifies so if (options.EmulationMode == EmulationMode.UntilHalt && (Cpu.StateFlags & Z80StateFlags.Halted) != 0) { ExecutionCompletionReason = ExecutionCompletionReason.Halted; return(true); } // --- Notify each CPU-bound device that the current operation has been completed foreach (var device in _cpuBoundDevices) { device.OnCpuOperationCompleted(); } // --- Decide whether this frame has been completed HasFrameCompleted = !Cpu.IsInOpExecution && CurrentFrameTact >= FrameTacts; } // --- A physical frame has just been completed. Take care about screen refresh FrameCount++; Overflow = CurrentFrameTact % FrameTacts; // --- Notify devices that the current frame completed OnFrameCompleted(); // --- We exit the cycle when the render frame has completed ExecutionCompletionReason = ExecutionCompletionReason.RenderFrameCompleted; return(true); }
/// <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); }
/// <summary> /// The main execution cycle of the Spectrum VM /// </summary> /// <param name="token">Cancellation token</param> /// <param name="options">Execution options</param> /// <return>True, if the cycle completed; false, if it has been cancelled</return> public bool ExecuteCycle(CancellationToken token, ExecuteCycleOptions options) { ExecuteCycleOptions = options; // --- We use these variables to calculate wait time at the end of the frame var cycleStartTime = Clock.GetCounter(); var cycleFrameCount = 0; // --- We use this variable to check whether to stop in Debug mode var executedInstructionCount = -1; // --- Loop #1: The main cycle that goes on until cancelled while (!token.IsCancellationRequested) { if (_frameCompleted) { // --- This counter helps us to calculate where we are in the frame after // --- each CPU operation cycle LastFrameStartCpuTick = Cpu.Tacts - Overflow; // --- Notify devices to start a new frame OnNewFrame(); LastRenderedUlaTact = Overflow; _frameCompleted = false; } // --- We use this timestamp for debug information calculation var currentFrameStartCounter = Clock.GetCounter(); DebugInfoProvider.CpuTime = 0; DebugInfoProvider.ScreenRenderingTime = 0; // --- Loop #2: The physical frame cycle that goes on while CPU and ULA // --- processes everything whithin a physical frame (0.019968 second) while (!_frameCompleted) { // --- Check debug mode when a CPU instruction has been entirelly executed if (!Cpu.IsInOpExecution) { // --- Check for cancellation if (token.IsCancellationRequested) { return(false); } // --- The next instruction is about to be executed executedInstructionCount++; // --- Check for reaching the termination point if (options.EmulationMode == EmulationMode.UntilExecutionPoint && options.TerminationPoint == Cpu.Registers.PC) { // --- We reached the termination point return(true); } // --- Check for a debugging stop point if (options.EmulationMode == EmulationMode.Debugger) { if (IsDebugStop(options.DebugStepMode, executedInstructionCount)) { // --- At this point, the cycle should be stopped because of debugging reasons // --- The screen should be refreshed ScreenDevice.OnFrameCompleted(); return(true); } } } // --- Check for interrupt signal generation InterruptDevice.CheckForInterrupt(CurrentFrameTact); // --- Run a single Z80 instruction var cpuStart = Clock.GetCounter(); Cpu.ExecuteCpuCycle(); DebugInfoProvider.CpuTime += Clock.GetCounter() - cpuStart; // --- Run a rendering cycle according to the current CPU tact count var lastTact = CurrentFrameTact; var renderStart = Clock.GetCounter(); ScreenDevice.RenderScreen(LastRenderedUlaTact + 1, lastTact); DebugInfoProvider.ScreenRenderingTime += Clock.GetCounter() - renderStart; LastRenderedUlaTact = lastTact; // --- Exit if the emulation mode specifies so if (options.EmulationMode == EmulationMode.UntilHalt && (Cpu.StateFlags & Z80StateFlags.Halted) != 0) { return(true); } // --- Notify each CPU-bound device that the current operation has been completed foreach (var device in _cpuBoundDevices) { device.OnCpuOperationCompleted(); } // --- Decide whether this frame has been completed _frameCompleted = !Cpu.IsInOpExecution && CurrentFrameTact >= _frameTacts; } // -- End Loop #2 // --- A physical frame has just been completed. Take care about screen refresh cycleFrameCount++; FrameCount++; // --- Calculate debug information DebugInfoProvider.FrameTime = Clock.GetCounter() - currentFrameStartCounter; DebugInfoProvider.UtilityTime = DebugInfoProvider.FrameTime - DebugInfoProvider.CpuTime - DebugInfoProvider.ScreenRenderingTime; DebugInfoProvider.CpuTimeInMs = DebugInfoProvider.CpuTime / (double)Clock.GetFrequency() * 1000; DebugInfoProvider.ScreenRenderingTimeInMs = DebugInfoProvider.ScreenRenderingTime / (double)Clock.GetFrequency() * 1000; DebugInfoProvider.UtilityTimeInMs = DebugInfoProvider.UtilityTime / (double)Clock.GetFrequency() * 1000; DebugInfoProvider.FrameTimeInMs = DebugInfoProvider.FrameTime / (double)Clock.GetFrequency() * 1000; // --- Notify devices that the current frame completed OnFrameCompleted(); // --- Exit if the emulation mode specifies so if (options.EmulationMode == EmulationMode.UntilFrameEnds) { return(true); } // --- Wait while the frame time ellapses var nextFrameCounter = cycleStartTime + cycleFrameCount * PhysicalFrameClockCount; Clock.WaitUntil((long)nextFrameCounter, token); // --- Start a new frame and carry on Overflow = CurrentFrameTact % _frameTacts; } // --- End Loop #1 // --- The cycle has been inerrupted by cancellation return(false); }