| 1 | package com.rox; | |
| 2 | ||
| 3 | import com.rox.apu.APU; | |
| 4 | import com.rox.audio.AudioOutput; | |
| 5 | import com.rox.audio.Resampler; | |
| 6 | import com.rox.audio.SpeakerAudioOutput; | |
| 7 | import com.rox.cartridge.Cartridge; | |
| 8 | import com.rox.clock.Clock; | |
| 9 | import com.rox.clock.FPSClock; | |
| 10 | import com.rox.clock.TickRateMonitor; | |
| 11 | import com.rox.cpu.mos6502.MOS6502; | |
| 12 | import com.rox.debug.cpu.InstructionTrace; | |
| 13 | import com.rox.debug.memory.MemoryEditor; | |
| 14 | import com.rox.input.ControllerConfiguration; | |
| 15 | import com.rox.mem.*; | |
| 16 | import com.rox.ppu.PPU; | |
| 17 | import com.rox.save.SystemSnapshot; | |
| 18 | import com.rox.time.SystemTimeSource; | |
| 19 | import com.rox.time.ThreadSleeper; | |
| 20 | import com.rox.video.VideoOutput; | |
| 21 | ||
| 22 | import javax.sound.sampled.LineUnavailableException; | |
| 23 | import java.util.Map; | |
| 24 | import java.util.concurrent.CountDownLatch; | |
| 25 | import java.util.zip.CRC32; | |
| 26 | ||
| 27 | public class NES { | |
| 28 | private static final long CPU_HZ = 1_789_773; | |
| 29 | /** | |
| 30 | * Far beyond any real instruction (7 cycles max, plus a 514-cycle OAM DMA stall and a 7-cycle | |
| 31 | * interrupt entry) - only reached if the CPU somehow never finishes, which would otherwise hang the | |
| 32 | * UI thread that asked for the step. | |
| 33 | */ | |
| 34 | static final int MAX_CYCLES_PER_STEPPED_INSTRUCTION = 10_000; | |
| 35 | /** Over three NTSC frames (29,780.5 CPU cycles each) - only reached if the PPU somehow stops finishing frames. */ | |
| 36 | static final int MAX_CYCLES_PER_STEPPED_FRAME = 100_000; | |
| 37 | /** The NES's 2KB of CPU RAM - NESMemoryBus mirrors it across $0000-$1FFF. */ | |
| 38 | private static final int CPU_RAM_SIZE = NESMemoryBus.CPU_RAM_MIRROR_MASK + 1; | |
| 39 | private static final long AUDIO_SAMPLE_RATE_HZ = 44_100; | |
| 40 | /** | |
| 41 | * Gives the JVM's JIT time to compile the emulation's hot per-cycle tick loop, and lets the ring buffer build | |
| 42 | * up a cushion, before audio playback (and thus underrun risk) begins. See {@link SpeakerAudioOutput} class doc | |
| 43 | * for why draining starts empty otherwise | |
| 44 | */ | |
| 45 | private static final long AUDIO_WARM_UP_MILLIS = 300; | |
| 46 | ||
| 47 | private final MOS6502 cpu; | |
| 48 | private final APU apu; | |
| 49 | private final PPU ppu; | |
| 50 | private final Clock clock; | |
| 51 | private final LatchedMemoryBus memoryBus; | |
| 52 | private final AudioOutput audioOutput; | |
| 53 | private final RAM ram; | |
| 54 | private final Cartridge cartridge; | |
| 55 | private final TickRateMonitor tickRateMonitor; | |
| 56 | private final MemoryEditor memoryEditor; | |
| 57 | private final InstructionTrace instructionTrace; | |
| 58 | /** set only once pause() has genuinely parked the clock - the precondition for capturing/restoring state while powered on (see captureSnapshot()) */ | |
| 59 | private volatile boolean paused; | |
| 60 | /** every frame handed to videoOutput - what stepFrame() watches for */ | |
| 61 | private volatile long framesPresented; | |
| 62 | /** set by {@link #powerOff()}, so {@link #powerOn()} skips warm-up and audio once a shutdown is asked for */ | |
| 63 | private volatile boolean stopRequested; | |
| 64 | /** guards {@link #powerOn()}'s setup and audio start against {@link #powerOff()} */ | |
| 65 | private final Object lifecycleLock = new Object(); | |
| 66 | ||
| 67 | public NES(final Cartridge cartridge) throws LineUnavailableException { | |
| 68 | this(new SpeakerAudioOutput(), VideoOutput.NO_OP, ControllerConfiguration.NONE, cartridge); | |
| 69 | } | |
| 70 | ||
| 71 | /** Like {@link #NES(Cartridge)}, but presenting frames to {@code videoOutput} as they complete. */ | |
| 72 | public NES(final VideoOutput videoOutput, final Cartridge cartridge) throws LineUnavailableException { | |
| 73 | this(new SpeakerAudioOutput(), videoOutput, ControllerConfiguration.NONE, cartridge); | |
| 74 | } | |
| 75 | ||
| 76 | /** Like {@link #NES(VideoOutput, Cartridge)}, but driven by {@code controllers} rather than {@link ControllerConfiguration#NONE}. */ | |
| 77 | public NES(final VideoOutput videoOutput, final ControllerConfiguration controllers, final Cartridge cartridge) throws LineUnavailableException { | |
| 78 | this(new SpeakerAudioOutput(), videoOutput, controllers, cartridge); | |
| 79 | } | |
| 80 | ||
| 81 | NES(final AudioOutput audioOutput, final Cartridge cartridge){ | |
| 82 | this(audioOutput, VideoOutput.NO_OP, ControllerConfiguration.NONE, cartridge); | |
| 83 | } | |
| 84 | ||
| 85 | NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final Cartridge cartridge){ | |
| 86 | this(audioOutput, videoOutput, ControllerConfiguration.NONE, cartridge); | |
| 87 | } | |
| 88 | ||
| 89 | NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final ControllerConfiguration controllers, final Cartridge cartridge){ | |
| 90 | this(audioOutput, videoOutput, controllers, cartridge, new FPSClock(CPU_HZ, 60, new SystemTimeSource(), new ThreadSleeper())); | |
| 91 | } | |
| 92 | ||
| 93 | /** Test-only entry point for injecting a fake {@link Clock} - see {@code NESTest}'s race-reproducing double. */ | |
| 94 | NES(final AudioOutput audioOutput, final Cartridge cartridge, final Clock clock){ | |
| 95 | this(audioOutput, VideoOutput.NO_OP, cartridge, clock); | |
| 96 | } | |
| 97 | ||
| 98 | /** Test-only entry point for injecting both a fake {@link Clock} and a mocked {@link VideoOutput}. */ | |
| 99 | NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final Cartridge cartridge, final Clock clock){ | |
| 100 | this(audioOutput, videoOutput, ControllerConfiguration.NONE, cartridge, clock); | |
| 101 | } | |
| 102 | ||
| 103 | /** Test-only entry point for injecting a fake {@link Clock} alongside a real {@link ControllerConfiguration}. */ | |
| 104 | NES(final AudioOutput audioOutput, final VideoOutput videoOutput, final ControllerConfiguration controllers, | |
| 105 | final Cartridge cartridge, final Clock clock){ | |
| 106 | this.ram = new RAM(CPU_RAM_SIZE); | |
| 107 | this.cartridge = cartridge; | |
| 108 | final MemoryBus ramBus = new MemoryBus8Bit(ram); | |
| 109 | //DMC's own sample-address generator (see DMCChannel) only ever produces addresses in | |
| 110 | //$8000-$FFFF (base $C000+, wrapping no lower than $8000) - always within cartridge range, | |
| 111 | //so the cartridge alone is a complete, correct DMA source with no need to route through | |
| 112 | //NESMemoryBus (which would need apu itself to construct, a circular dependency) | |
| 113 | this.apu = new APU(cartridge); | |
| 114 | this.ppu = new PPU(cartridge); | |
| 115 | this.memoryBus = new Latched8BitMemoryBus(new NESMemoryBus(ramBus, apu, cartridge, ppu, controllers)); | |
| 116 | this.cpu = new MOS6502(memoryBus); | |
| 117 | this.tickRateMonitor = new TickRateMonitor(new SystemTimeSource()); | |
| 118 | this.memoryEditor = new MemoryEditor(ram::write, cartridge::writePrgRamWithoutSaving); | |
| 119 | this.instructionTrace = new InstructionTrace(cpu::isAtInstructionBoundary, cpu::programCounter, | |
| 120 | DebugCapture.sideEffectFreePeek(ram::read, cartridge), cpu::packedRegisters, cpu::packedFlags, | |
| 121 | InstructionTrace.DEFAULT_CAPACITY); | |
| 122 | this.clock = clock; | |
| 123 | this.audioOutput = audioOutput; | |
| 124 | ||
| 125 | final Resampler resampler = new Resampler(CPU_HZ, AUDIO_SAMPLE_RATE_HZ); | |
| 126 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(tickRateMonitor); |
| 127 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(cpu); |
| 128 | //right after the CPU, so a locked byte is back at its value before anything else sees the tick's state | |
| 129 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(memoryEditor); |
| 130 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(apu); |
| 131 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(ppu); |
| 132 |
4
1. lambda$new$0 : negated conditional → KILLED 2. <init> : removed call to com/rox/clock/Clock::addListener → KILLED 3. lambda$new$0 : removed call to com/rox/cpu/mos6502/MOS6502::setIRQLine → KILLED 4. lambda$new$0 : negated conditional → KILLED |
clock.addListener(() -> cpu.setIRQLine(apu.isIrqAsserted() || cartridge.isIrqAsserted())); |
| 133 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(() -> { |
| 134 |
1
1. lambda$new$1 : negated conditional → KILLED |
if (ppu.consumeNmiEdge()){ |
| 135 |
1
1. lambda$new$1 : removed call to com/rox/cpu/mos6502/MOS6502::signalNMI → KILLED |
cpu.signalNMI(); |
| 136 | } | |
| 137 | }); | |
| 138 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(() -> { |
| 139 | final int stallCycles = ppu.consumeOamDmaStallCycles(); | |
| 140 |
2
1. lambda$new$2 : changed conditional boundary → SURVIVED 2. lambda$new$2 : negated conditional → KILLED |
if (stallCycles > 0){ |
| 141 |
1
1. lambda$new$2 : removed call to com/rox/cpu/mos6502/MOS6502::stall → KILLED |
cpu.stall(stallCycles); |
| 142 | } | |
| 143 | }); | |
| 144 | //after the OAM DMA stall listener above, so a stall starting this tick holds recording back | |
| 145 | //rather than the same instruction being recorded twice - see InstructionTrace's class doc | |
| 146 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(instructionTrace); |
| 147 |
2
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED 2. lambda$new$3 : removed call to java/util/OptionalDouble::ifPresent → KILLED |
clock.addListener(() -> resampler.accept(apu.outputSample()).ifPresent(audioOutput::write)); |
| 148 |
1
1. <init> : removed call to com/rox/clock/Clock::addListener → KILLED |
clock.addListener(() -> { |
| 149 | //polling the (possibly real, physical) gamepads is folded into this same frame-ready gate | |
| 150 | //rather than a second, separate clock.addListener - PPU.consumeFrameReady() is a one-shot | |
| 151 | //flag (it resets itself the moment it's read true), so a second listener also calling it | |
| 152 | //would simply never see true, having already been beaten to it by this one | |
| 153 |
1
1. lambda$new$4 : negated conditional → KILLED |
if (ppu.consumeFrameReady()){ |
| 154 |
1
1. lambda$new$4 : Replaced long addition with subtraction → KILLED |
framesPresented++; |
| 155 |
1
1. lambda$new$4 : removed call to com/rox/video/VideoOutput::present → KILLED |
videoOutput.present(ppu.rgbFramebuffer()); |
| 156 |
1
1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED |
controllers.player1().poll(); |
| 157 |
1
1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED |
controllers.player2().poll(); |
| 158 |
1
1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED |
controllers.player3().poll(); |
| 159 |
1
1. lambda$new$4 : removed call to com/rox/input/Controller::poll → KILLED |
controllers.player4().poll(); |
| 160 | } | |
| 161 | }); | |
| 162 | } | |
| 163 | ||
| 164 | public void powerOn(){ | |
| 165 | //signalled once the clock thread is executing, so a slow thread start can't eat into the warm-up | |
| 166 | final CountDownLatch clockStarted = new CountDownLatch(1); | |
| 167 | final Thread clockThread = new Thread(() -> { | |
| 168 |
1
1. lambda$powerOn$0 : removed call to java/util/concurrent/CountDownLatch::countDown → TIMED_OUT |
clockStarted.countDown(); |
| 169 |
1
1. lambda$powerOn$0 : removed call to com/rox/clock/Clock::run → KILLED |
clock.run(); |
| 170 | }); | |
| 171 | synchronized (lifecycleLock){ | |
| 172 |
1
1. powerOn : removed call to com/rox/cpu/mos6502/MOS6502::reset → KILLED |
cpu.reset(); |
| 173 | stopRequested = false; | |
| 174 |
1
1. powerOn : removed call to com/rox/clock/Clock::prepareToRun → TIMED_OUT |
clock.prepareToRun(); //a stop() from here on ends the run, even one landing before run() starts |
| 175 |
1
1. powerOn : removed call to java/lang/Thread::start → TIMED_OUT |
clockThread.start(); |
| 176 | } | |
| 177 | ||
| 178 | boolean interrupted = false; | |
| 179 | try { | |
| 180 |
1
1. powerOn : removed call to java/util/concurrent/CountDownLatch::await → SURVIVED |
clockStarted.await(); |
| 181 |
1
1. powerOn : negated conditional → KILLED |
if (!stopRequested){ |
| 182 |
1
1. powerOn : removed call to java/lang/Thread::sleep → KILLED |
Thread.sleep(AUDIO_WARM_UP_MILLIS); |
| 183 | } | |
| 184 | } catch (InterruptedException e){ | |
| 185 | interrupted = true; | |
| 186 | } | |
| 187 | synchronized (lifecycleLock){ | |
| 188 | //under the lock, so a powerOff() can't land between this check and start() and leave audio on | |
| 189 |
2
1. powerOn : negated conditional → KILLED 2. powerOn : negated conditional → KILLED |
if (!interrupted && !stopRequested){ |
| 190 |
1
1. powerOn : removed call to com/rox/audio/AudioOutput::start → KILLED |
audioOutput.start(); |
| 191 | } | |
| 192 | } | |
| 193 | ||
| 194 |
1
1. powerOn : negated conditional → TIMED_OUT |
if (interrupted){ |
| 195 |
1
1. powerOn : removed call to com/rox/clock/Clock::stop → TIMED_OUT |
clock.stop(); |
| 196 | } | |
| 197 | //retry join() until the thread has genuinely terminated - a single interrupted join() would | |
| 198 | //otherwise return early without actually waiting, silently leaving the clock thread running | |
| 199 |
1
1. powerOn : negated conditional → TIMED_OUT |
while (clockThread.isAlive()){ |
| 200 | try { | |
| 201 |
1
1. powerOn : removed call to java/lang/Thread::join → TIMED_OUT |
clockThread.join(); |
| 202 | } catch (InterruptedException e){ | |
| 203 | //an interrupt caught only here (audio already started) must still stop the clock - nothing else will | |
| 204 | interrupted = true; | |
| 205 |
1
1. powerOn : removed call to com/rox/clock/Clock::stop → TIMED_OUT |
clock.stop(); |
| 206 | } | |
| 207 | } | |
| 208 | //standard practice even though nothing in this method (or its callers) observes the flag | |
| 209 | //afterward - powerOn() returns right after this, so this is an accepted, unobservable pitest | |
| 210 | //survivor rather than something worth chasing a test for | |
| 211 |
1
1. powerOn : negated conditional → SURVIVED |
if (interrupted){ |
| 212 |
1
1. powerOn : removed call to java/lang/Thread::interrupt → SURVIVED |
Thread.currentThread().interrupt(); |
| 213 | } | |
| 214 | } | |
| 215 | ||
| 216 | /** | |
| 217 | * Freezes a running emulation without resetting or tearing anything down: the clock parks at the | |
| 218 | * end of its current frame, then the CPU is single-stepped to the end of whatever instruction it | |
| 219 | * was part-way through - so CPU state is fully captured by {@code getEnvironmentSnapshot()} for as | |
| 220 | * long as the pause lasts - and audio is silenced. {@link #resume()} carries on from exactly there. | |
| 221 | * | |
| 222 | * @return false if there was nothing running to pause | |
| 223 | */ | |
| 224 | public boolean pause(){ | |
| 225 |
1
1. pause : negated conditional → KILLED |
if (!clock.pause()){ |
| 226 |
1
1. pause : replaced boolean return with true for com/rox/NES::pause → KILLED |
return false; |
| 227 | } | |
| 228 | //safe to tick from this thread: a true pause() guarantees the clock thread is parked | |
| 229 |
1
1. pause : removed call to com/rox/NES::finishInFlightInstruction → KILLED |
finishInFlightInstruction(); |
| 230 |
1
1. pause : removed call to com/rox/audio/AudioOutput::pause → KILLED |
audioOutput.pause(); |
| 231 | paused = true; | |
| 232 |
1
1. pause : replaced boolean return with false for com/rox/NES::pause → KILLED |
return true; |
| 233 | } | |
| 234 | ||
| 235 | /** True from a successful {@link #pause()} until {@link #resume()}. */ | |
| 236 | public boolean isPaused(){ | |
| 237 |
2
1. isPaused : replaced boolean return with true for com/rox/NES::isPaused → KILLED 2. isPaused : replaced boolean return with false for com/rox/NES::isPaused → KILLED |
return paused; |
| 238 | } | |
| 239 | ||
| 240 | /** Carry on after a {@link #pause()}; does nothing if not paused. */ | |
| 241 | public void resume(){ | |
| 242 | paused = false; | |
| 243 |
1
1. resume : removed call to com/rox/audio/AudioOutput::resume → KILLED |
audioOutput.resume(); |
| 244 |
1
1. resume : removed call to com/rox/clock/Clock::resume → KILLED |
clock.resume(); |
| 245 | } | |
| 246 | ||
| 247 | /** | |
| 248 | * The whole system's state, for a save state or debug capture. Only while {@link #pause()}d (or | |
| 249 | * before/after the clock runs at all) - otherwise the clock thread would be changing it mid-copy. | |
| 250 | * A clock that simply stopped (e.g. after {@link #powerOff()}) can leave the CPU part-way through an | |
| 251 | * instruction, unlike a pause; that instruction is finished first, so this may advance the system | |
| 252 | * by a few cycles. | |
| 253 | * | |
| 254 | * <p>Not captured: controller shift registers (a pause landing mid-controller-read could misread | |
| 255 | * one frame of input after a restore), and the audio resampler's fractional position. | |
| 256 | * | |
| 257 | * @throws IllegalStateException if the clock is running and not paused | |
| 258 | */ | |
| 259 | public SystemSnapshot captureSnapshot(){ | |
| 260 |
1
1. captureSnapshot : removed call to com/rox/NES::requireNothingTicking → KILLED |
requireNothingTicking(); |
| 261 |
1
1. captureSnapshot : removed call to com/rox/NES::finishInFlightInstruction → KILLED |
finishInFlightInstruction(); |
| 262 |
1
1. captureSnapshot : replaced return value with null for com/rox/NES::captureSnapshot → KILLED |
return new SystemSnapshot(romCrc32(cartridge), cpu.snapshot(), ppu.snapshot(), apu.snapshot(), ram.snapshot(), |
| 263 | cartridge.snapshot()); | |
| 264 | } | |
| 265 | ||
| 266 | /** | |
| 267 | * Puts the whole system back as {@code snapshot} captured it. Same "paused or not running" | |
| 268 | * precondition as {@link #captureSnapshot()}. | |
| 269 | * | |
| 270 | * @throws IllegalStateException if the clock is running and not paused | |
| 271 | * @throws IllegalArgumentException if {@code snapshot} was taken with a different cartridge, or any of | |
| 272 | * its parts is the wrong shape - checked up front, so a rejected snapshot changes nothing | |
| 273 | */ | |
| 274 | public void restoreSnapshot(final SystemSnapshot snapshot){ | |
| 275 |
1
1. restoreSnapshot : removed call to com/rox/NES::requireNothingTicking → KILLED |
requireNothingTicking(); |
| 276 |
1
1. restoreSnapshot : negated conditional → KILLED |
if (snapshot.romCrc32() != romCrc32(cartridge)){ |
| 277 | throw new IllegalArgumentException("Snapshot was taken with a different cartridge"); | |
| 278 | } | |
| 279 | //every component that can reject a malformed snapshot (array sizes, mapper type) is checked before | |
| 280 | //any is restored - a CRC-valid save file can still decode to the wrong shapes, and stopping | |
| 281 | //part-way would leave a mix of old and restored state. The APU and CPU snapshots are plain | |
| 282 | //values with nothing to reject. | |
| 283 |
1
1. restoreSnapshot : removed call to com/rox/cartridge/Cartridge::checkRestorable → SURVIVED |
cartridge.checkRestorable(snapshot.mapper()); |
| 284 |
1
1. restoreSnapshot : removed call to com/rox/mem/RAM::checkRestorable → KILLED |
ram.checkRestorable(snapshot.ram()); |
| 285 |
1
1. restoreSnapshot : removed call to com/rox/ppu/PPU::checkRestorable → KILLED |
ppu.checkRestorable(snapshot.ppu()); |
| 286 |
1
1. restoreSnapshot : removed call to com/rox/cartridge/Cartridge::restore → KILLED |
cartridge.restore(snapshot.mapper()); |
| 287 |
1
1. restoreSnapshot : removed call to com/rox/mem/RAM::restore → KILLED |
ram.restore(snapshot.ram()); |
| 288 |
1
1. restoreSnapshot : removed call to com/rox/ppu/PPU::restore → KILLED |
ppu.restore(snapshot.ppu()); |
| 289 |
1
1. restoreSnapshot : removed call to com/rox/apu/APU::restore → KILLED |
apu.restore(snapshot.apu()); |
| 290 |
1
1. restoreSnapshot : removed call to com/rox/cpu/mos6502/MOS6502::restore → KILLED |
cpu.restore(snapshot.cpu()); |
| 291 | } | |
| 292 | ||
| 293 | /** Ticks until the CPU is between instructions - callers must already know nothing else is ticking. */ | |
| 294 | /** | |
| 295 | * Runs exactly one instruction - from wherever the CPU is, to the next instruction boundary (so a | |
| 296 | * part-finished instruction is just finished). An OAM DMA stall or interrupt entry that follows is | |
| 297 | * part of the same step. Only while {@link #pause()}d (or with the clock not running), like | |
| 298 | * {@link #captureSnapshot()}. | |
| 299 | * | |
| 300 | * @throws IllegalStateException if the clock is running and not paused, or the CPU doesn't reach an | |
| 301 | * instruction boundary within {@link #MAX_CYCLES_PER_STEPPED_INSTRUCTION} cycles | |
| 302 | */ | |
| 303 | public void stepInstruction(){ | |
| 304 |
1
1. stepInstruction : removed call to com/rox/NES::requireNothingTicking → KILLED |
requireNothingTicking(); |
| 305 | int cycles = 0; | |
| 306 | do { | |
| 307 |
2
1. stepInstruction : Changed increment from 1 to -1 → KILLED 2. stepInstruction : negated conditional → KILLED |
if (cycles++ == MAX_CYCLES_PER_STEPPED_INSTRUCTION){ |
| 308 | throw new IllegalStateException("CPU didn't finish an instruction within " | |
| 309 | + MAX_CYCLES_PER_STEPPED_INSTRUCTION + " cycles"); | |
| 310 | } | |
| 311 |
1
1. stepInstruction : removed call to com/rox/clock/Clock::tick → KILLED |
clock.tick(); |
| 312 |
1
1. stepInstruction : negated conditional → KILLED |
} while (!cpu.isAtInstructionBoundary()); |
| 313 | } | |
| 314 | ||
| 315 | /** | |
| 316 | * Runs exactly {@code cycles} CPU cycles - which can leave the CPU part-way through an instruction. | |
| 317 | * Same "paused or not running" precondition as {@link #stepInstruction()}. | |
| 318 | * | |
| 319 | * @throws IllegalArgumentException if {@code cycles} isn't positive | |
| 320 | */ | |
| 321 | public void stepCycles(final int cycles){ | |
| 322 |
1
1. stepCycles : removed call to com/rox/NES::requireNothingTicking → KILLED |
requireNothingTicking(); |
| 323 |
2
1. stepCycles : changed conditional boundary → KILLED 2. stepCycles : negated conditional → KILLED |
if (cycles < 1){ |
| 324 | throw new IllegalArgumentException("Step at least one cycle, not " + cycles); | |
| 325 | } | |
| 326 |
2
1. stepCycles : changed conditional boundary → KILLED 2. stepCycles : negated conditional → KILLED |
for (int i = 0; i < cycles; i++){ |
| 327 |
1
1. stepCycles : removed call to com/rox/clock/Clock::tick → KILLED |
clock.tick(); |
| 328 | } | |
| 329 | } | |
| 330 | ||
| 331 | /** | |
| 332 | * Runs until the next frame is finished and presented (the start of vertical blanking), then | |
| 333 | * finishes the instruction in progress - so each step shows exactly one new frame and stops at the | |
| 334 | * same point in it. A fixed cycle count can't do that: an NTSC frame is 29,780.5 CPU cycles. Same | |
| 335 | * "paused or not running" precondition as {@link #stepInstruction()}. | |
| 336 | * | |
| 337 | * @throws IllegalStateException if the clock is running and not paused, or no frame finishes within | |
| 338 | * {@link #MAX_CYCLES_PER_STEPPED_FRAME} cycles | |
| 339 | */ | |
| 340 | public void stepFrame(){ | |
| 341 |
1
1. stepFrame : removed call to com/rox/NES::stepFrame → KILLED |
stepFrame(MAX_CYCLES_PER_STEPPED_FRAME); |
| 342 | } | |
| 343 | ||
| 344 | /** {@link #stepFrame()} with a given cap - a test seam, since a real PPU always finishes a frame well inside the real one. */ | |
| 345 | void stepFrame(final int maxCycles){ | |
| 346 |
1
1. stepFrame : removed call to com/rox/NES::requireNothingTicking → KILLED |
requireNothingTicking(); |
| 347 | final long startingFrame = framesPresented; | |
| 348 | int cycles = 0; | |
| 349 |
2
1. stepFrame : changed conditional boundary → KILLED 2. stepFrame : negated conditional → KILLED |
while (framesPresented <= startingFrame){ |
| 350 |
2
1. stepFrame : Changed increment from 1 to -1 → KILLED 2. stepFrame : negated conditional → KILLED |
if (cycles++ == maxCycles){ |
| 351 | throw new IllegalStateException("No frame finished within " + maxCycles + " cycles"); | |
| 352 | } | |
| 353 |
1
1. stepFrame : removed call to com/rox/clock/Clock::tick → KILLED |
clock.tick(); |
| 354 | } | |
| 355 |
1
1. stepFrame : removed call to com/rox/NES::finishInFlightInstruction → KILLED |
finishInFlightInstruction(); |
| 356 | } | |
| 357 | ||
| 358 | private void finishInFlightInstruction(){ | |
| 359 |
1
1. finishInFlightInstruction : negated conditional → KILLED |
while (!cpu.isAtInstructionBoundary()){ |
| 360 |
1
1. finishInFlightInstruction : removed call to com/rox/clock/Clock::tick → TIMED_OUT |
clock.tick(); |
| 361 | } | |
| 362 | } | |
| 363 | ||
| 364 | /** | |
| 365 | * A debugging edit: sets one byte of CPU RAM ({@code $0000-$1FFF}, mirrored) or PRG-RAM | |
| 366 | * ({@code $6000-$7FFF}) any time. A PRG-RAM edit doesn't trigger a battery save. | |
| 367 | * <ul> | |
| 368 | * <li><b>running?</b>: applied by the clock thread at its next tick, never written from this thread into memory the emulation is using.</li> | |
| 369 | * <li><b>paused/not running?</b>: applied now!</li> | |
| 370 | * </ul> | |
| 371 | * | |
| 372 | * @throws IllegalArgumentException for any other address, or a value outside 0-255 | |
| 373 | */ | |
| 374 | public void pokeMemory(final int address, final int value){ | |
| 375 |
1
1. pokeMemory : negated conditional → KILLED |
if (nothingTicking()){ |
| 376 |
1
1. pokeMemory : removed call to com/rox/debug/memory/MemoryEditor::write → KILLED |
memoryEditor.write(address, value); |
| 377 | } else { | |
| 378 |
1
1. pokeMemory : removed call to com/rox/debug/memory/MemoryEditor::queue → KILLED |
memoryEditor.queue(address, value); |
| 379 | } | |
| 380 | } | |
| 381 | ||
| 382 | /** | |
| 383 | * Holds a byte at {@code value} - set now (as {@link #pokeMemory}), then re-applied every tick, so | |
| 384 | * whatever the game writes there doesn't stick - until {@link #unlockMemory}. | |
| 385 | * | |
| 386 | * @throws IllegalArgumentException as {@link #pokeMemory} | |
| 387 | */ | |
| 388 | public void lockMemory(final int address, final int value){ | |
| 389 |
1
1. lockMemory : removed call to com/rox/debug/memory/MemoryEditor::lock → KILLED |
memoryEditor.lock(address, value); |
| 390 |
1
1. lockMemory : removed call to com/rox/NES::pokeMemory → KILLED |
pokeMemory(address, value); |
| 391 | } | |
| 392 | ||
| 393 | public void unlockMemory(final int address){ | |
| 394 |
1
1. unlockMemory : removed call to com/rox/debug/memory/MemoryEditor::unlock → KILLED |
memoryEditor.unlock(address); |
| 395 | } | |
| 396 | ||
| 397 | /** Locked bytes, address to value, in address order (RAM by its real {@code $0000-$07FF} address). */ | |
| 398 | public Map<Integer, Integer> lockedMemory(){ | |
| 399 |
1
1. lockedMemory : replaced return value with Collections.emptyMap for com/rox/NES::lockedMemory → KILLED |
return memoryEditor.locks(); |
| 400 | } | |
| 401 | ||
| 402 | private boolean nothingTicking(){ | |
| 403 |
3
1. nothingTicking : negated conditional → KILLED 2. nothingTicking : replaced boolean return with true for com/rox/NES::nothingTicking → KILLED 3. nothingTicking : negated conditional → KILLED |
return !clock.isRunning() || paused; |
| 404 | } | |
| 405 | ||
| 406 | private void requireNothingTicking(){ | |
| 407 |
1
1. requireNothingTicking : negated conditional → KILLED |
if (!nothingTicking()){ |
| 408 | throw new IllegalStateException("Pause the NES before capturing, restoring or stepping it"); | |
| 409 | } | |
| 410 | } | |
| 411 | ||
| 412 | /** Identifies a cartridge's ROM (PRG then CHR) - what {@link SystemSnapshot#romCrc32()} is checked against. */ | |
| 413 | private static long romCrc32(final Cartridge cartridge){ | |
| 414 | final CRC32 crc = new CRC32(); | |
| 415 |
1
1. romCrc32 : removed call to java/util/zip/CRC32::update → KILLED |
crc.update(cartridge.rom().prgRom()); |
| 416 |
1
1. romCrc32 : removed call to java/util/zip/CRC32::update → KILLED |
crc.update(cartridge.rom().chrRom()); |
| 417 |
1
1. romCrc32 : replaced long return with 0 for com/rox/NES::romCrc32 → KILLED |
return crc.getValue(); |
| 418 | } | |
| 419 | ||
| 420 | /** | |
| 421 | * Stops the emulation for good. Returns only once nothing can tick any more - even when called while | |
| 422 | * {@link #powerOn()} is still starting the clock - so {@link #pokeMemory}, {@link #captureSnapshot} or a | |
| 423 | * step can then act from this thread safely. | |
| 424 | */ | |
| 425 | public void powerOff(){ | |
| 426 | synchronized (lifecycleLock){ | |
| 427 | stopRequested = true; | |
| 428 |
1
1. powerOff : removed call to com/rox/clock/Clock::stopAndWait → TIMED_OUT |
clock.stopAndWait(); |
| 429 |
1
1. powerOff : removed call to com/rox/audio/AudioOutput::stop → KILLED |
audioOutput.stop(); |
| 430 | } | |
| 431 | } | |
| 432 | ||
| 433 | MOS6502 cpu(){ | |
| 434 |
1
1. cpu : replaced return value with null for com/rox/NES::cpu → KILLED |
return cpu; |
| 435 | } | |
| 436 | ||
| 437 | APU apu(){ | |
| 438 |
1
1. apu : replaced return value with null for com/rox/NES::apu → KILLED |
return apu; |
| 439 | } | |
| 440 | ||
| 441 | PPU ppu(){ | |
| 442 |
1
1. ppu : replaced return value with null for com/rox/NES::ppu → KILLED |
return ppu; |
| 443 | } | |
| 444 | ||
| 445 | RAM ram(){ | |
| 446 |
1
1. ram : replaced return value with null for com/rox/NES::ram → KILLED |
return ram; |
| 447 | } | |
| 448 | ||
| 449 | TickRateMonitor tickRateMonitor(){ | |
| 450 |
1
1. tickRateMonitor : replaced return value with null for com/rox/NES::tickRateMonitor → KILLED |
return tickRateMonitor; |
| 451 | } | |
| 452 | ||
| 453 | InstructionTrace instructionTrace(){ | |
| 454 |
1
1. instructionTrace : replaced return value with null for com/rox/NES::instructionTrace → KILLED |
return instructionTrace; |
| 455 | } | |
| 456 | ||
| 457 | /** The CPU clock rate a real NTSC NES runs at - what {@link #tickRateMonitor()}'s measured rate should match. */ | |
| 458 | static long intendedCpuHz(){ | |
| 459 |
1
1. intendedCpuHz : replaced long return with 0 for com/rox/NES::intendedCpuHz → KILLED |
return CPU_HZ; |
| 460 | } | |
| 461 | ||
| 462 | Cartridge cartridge(){ | |
| 463 |
1
1. cartridge : replaced return value with null for com/rox/NES::cartridge → KILLED |
return cartridge; |
| 464 | } | |
| 465 | ||
| 466 | Clock clock(){ | |
| 467 |
1
1. clock : replaced return value with null for com/rox/NES::clock → KILLED |
return clock; |
| 468 | } | |
| 469 | } | |
Mutations | ||
| 126 |
1.1 |
|
| 127 |
1.1 |
|
| 129 |
1.1 |
|
| 130 |
1.1 |
|
| 131 |
1.1 |
|
| 132 |
1.1 2.2 3.3 4.4 |
|
| 133 |
1.1 |
|
| 134 |
1.1 |
|
| 135 |
1.1 |
|
| 138 |
1.1 |
|
| 140 |
1.1 2.2 |
|
| 141 |
1.1 |
|
| 146 |
1.1 |
|
| 147 |
1.1 2.2 |
|
| 148 |
1.1 |
|
| 153 |
1.1 |
|
| 154 |
1.1 |
|
| 155 |
1.1 |
|
| 156 |
1.1 |
|
| 157 |
1.1 |
|
| 158 |
1.1 |
|
| 159 |
1.1 |
|
| 168 |
1.1 |
|
| 169 |
1.1 |
|
| 172 |
1.1 |
|
| 174 |
1.1 |
|
| 175 |
1.1 |
|
| 180 |
1.1 |
|
| 181 |
1.1 |
|
| 182 |
1.1 |
|
| 189 |
1.1 2.2 |
|
| 190 |
1.1 |
|
| 194 |
1.1 |
|
| 195 |
1.1 |
|
| 199 |
1.1 |
|
| 201 |
1.1 |
|
| 205 |
1.1 |
|
| 211 |
1.1 |
|
| 212 |
1.1 |
|
| 225 |
1.1 |
|
| 226 |
1.1 |
|
| 229 |
1.1 |
|
| 230 |
1.1 |
|
| 232 |
1.1 |
|
| 237 |
1.1 2.2 |
|
| 243 |
1.1 |
|
| 244 |
1.1 |
|
| 260 |
1.1 |
|
| 261 |
1.1 |
|
| 262 |
1.1 |
|
| 275 |
1.1 |
|
| 276 |
1.1 |
|
| 283 |
1.1 |
|
| 284 |
1.1 |
|
| 285 |
1.1 |
|
| 286 |
1.1 |
|
| 287 |
1.1 |
|
| 288 |
1.1 |
|
| 289 |
1.1 |
|
| 290 |
1.1 |
|
| 304 |
1.1 |
|
| 307 |
1.1 2.2 |
|
| 311 |
1.1 |
|
| 312 |
1.1 |
|
| 322 |
1.1 |
|
| 323 |
1.1 2.2 |
|
| 326 |
1.1 2.2 |
|
| 327 |
1.1 |
|
| 341 |
1.1 |
|
| 346 |
1.1 |
|
| 349 |
1.1 2.2 |
|
| 350 |
1.1 2.2 |
|
| 353 |
1.1 |
|
| 355 |
1.1 |
|
| 359 |
1.1 |
|
| 360 |
1.1 |
|
| 375 |
1.1 |
|
| 376 |
1.1 |
|
| 378 |
1.1 |
|
| 389 |
1.1 |
|
| 390 |
1.1 |
|
| 394 |
1.1 |
|
| 399 |
1.1 |
|
| 403 |
1.1 2.2 3.3 |
|
| 407 |
1.1 |
|
| 415 |
1.1 |
|
| 416 |
1.1 |
|
| 417 |
1.1 |
|
| 428 |
1.1 |
|
| 429 |
1.1 |
|
| 434 |
1.1 |
|
| 438 |
1.1 |
|
| 442 |
1.1 |
|
| 446 |
1.1 |
|
| 450 |
1.1 |
|
| 454 |
1.1 |
|
| 459 |
1.1 |
|
| 463 |
1.1 |
|
| 467 |
1.1 |