Skip to content

Commit

Permalink
Save/Restore state works
Browse files Browse the repository at this point in the history
  • Loading branch information
felipecsl committed Oct 7, 2018
1 parent 1135ea7 commit ee2b165
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 46 deletions.
64 changes: 39 additions & 25 deletions android/app/src/main/kotlin/com/felipecsl/knes/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.felipecsl.knes.app

import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
Expand All @@ -18,7 +17,6 @@ import androidx.core.content.ContextCompat
import com.felipecsl.android.NesGLSurfaceView
import com.felipecsl.knes.*
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar

class MainActivity : AppCompatActivity(), Runnable {
Expand All @@ -39,6 +37,8 @@ class MainActivity : AppCompatActivity(), Runnable {
private var handler: Handler
private var isRunning = false
private var isPaused = false
@Volatile private var shouldSaveState = false
@Volatile private var shouldRestoreState = false
private val audioEngine = AudioEngineWrapper()
private lateinit var director: Director
private val buttons = BooleanArray(8)
Expand Down Expand Up @@ -122,12 +122,10 @@ class MainActivity : AppCompatActivity(), Runnable {
private fun startConsole(cartridgeData: ByteArray, glSprite: GLSprite) {
audioEngine.start()
if (implSwitch.isChecked) {
Snackbar.make(implSwitch, "Using Kotlin/Native implementation",
BaseTransientBottomBar.LENGTH_SHORT).show()
Snackbar.make(implSwitch, "Using Kotlin/Native implementation", Snackbar.LENGTH_SHORT).show()
nativeStartConsole(cartridgeData)
} else {
Snackbar.make(implSwitch, "Using JVM implementation",
BaseTransientBottomBar.LENGTH_SHORT).show()
Snackbar.make(implSwitch, "Using JVM implementation", Snackbar.LENGTH_SHORT).show()
director = Director(cartridgeData)
glSprite.director = director
staticConsole = director.console
Expand All @@ -136,6 +134,8 @@ class MainActivity : AppCompatActivity(), Runnable {
}

override fun run() {
maybeSaveState()
maybeRestoreState()
director.run()
}

Expand All @@ -149,32 +149,46 @@ class MainActivity : AppCompatActivity(), Runnable {
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item!!.itemId) {
R.id.action_save_state -> saveState()
R.id.action_restore_state -> restoreState()
R.id.action_save_state -> {
director.pause()
shouldSaveState = true
handler.post(this)
return true
}
R.id.action_restore_state -> {
director.pause()
shouldRestoreState = true
handler.post(this)
return true
}
else -> super.onOptionsItemSelected(item)
}
}

private fun saveState(): Boolean {
val stateMap = director.dumpState()
val sharedPrefs = getSharedPreferences(STATE_PREFS_KEY, Context.MODE_PRIVATE)
sharedPrefs.edit().also { p ->
stateMap.forEach { (k, v) ->
p.putString(k, v)
}
}.apply()
Snackbar.make(implSwitch, "Game state saved", BaseTransientBottomBar.LENGTH_SHORT).show()
return true
private fun maybeSaveState() {
if (shouldSaveState) {
val stateMap = director.dumpState()
val sharedPrefs = getSharedPreferences(STATE_PREFS_KEY, Context.MODE_PRIVATE)
sharedPrefs.edit().also { p ->
stateMap.forEach { (k, v) ->
p.putString(k, v)
}
}.apply()
Snackbar.make(implSwitch, "Game state saved", Snackbar.LENGTH_SHORT).show()
shouldSaveState = false
}
}

private fun restoreState(): Boolean {
val sharedPrefs = getSharedPreferences(STATE_PREFS_KEY, Context.MODE_PRIVATE)
val state = sharedPrefs.all
if (state.isNotEmpty()) {
director.restoreState(state)
Snackbar.make(implSwitch, "Game state restored", BaseTransientBottomBar.LENGTH_SHORT).show()
private fun maybeRestoreState() {
if (shouldRestoreState) {
val sharedPrefs = getSharedPreferences(STATE_PREFS_KEY, Context.MODE_PRIVATE)
val state = sharedPrefs.all
if (state.isNotEmpty()) {
director.restoreState(state)
Snackbar.make(implSwitch, "Game state restored", Snackbar.LENGTH_SHORT).show()
}
shouldRestoreState = false
}
return true
}

companion object {
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/kotlin/com/felipecsl/knes/APU.kt
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,9 @@ internal class APU(
noiseEnvelopeStart, noiseEnvelopePeriod, noiseEnvelopeValue, noiseEnvelopeVolume,
noiseConstantVolume, dmcEnabled, dmcValue, dmcSampleAddress, dmcSampleLength,
dmcCurrentAddress, dmcCurrentLength, dmcShiftRegister, dmcBitCount, dmcTickPeriod,
dmcTickValue, dmcLoop, dmcIrq).joinToString("\n")
dmcTickValue, dmcLoop, dmcIrq).joinToString("\n").also {
println("APU state saved")
}
}

fun restoreState(state: String) {
Expand Down Expand Up @@ -401,6 +403,7 @@ internal class APU(
dmcTickValue = parts[i++].toInt()
dmcLoop = parts[i++].toBoolean()
dmcIrq = parts[i].toBoolean()
println("APU state restored")
}

fun writeRegister(address: Int, value: Int /* Byte */) {
Expand Down
12 changes: 8 additions & 4 deletions common/src/main/kotlin/com/felipecsl/knes/CPU.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class CPU(
private val apu: APU,
val controller1: Controller,
private val controller2: Controller,
private val ram: IntArray = IntArray(2048),
private var ram: IntArray = IntArray(2048),
private val stepCallback: CPUStepCallback? = null
) {
private var stepAddress: Int = 0
Expand Down Expand Up @@ -572,13 +572,16 @@ internal class CPU(
}

fun dumpState(): String {
return listOf(cycles, PC, SP, A, X, Y, C, Z, I, D, B, U, V, N, interrupt, stall)
.joinToString()
return listOf(ram.joinToString(), cycles, PC, SP, A, X, Y, C, Z, I, D, B, U, V, N, interrupt,
stall).joinToString("\n").also {
println("CPU state saved")
}
}

fun restoreState(state: String) {
val parts = state.split(", ")
val parts = state.split("\n")
var i = 0
ram = parts[i++].toIntArray()
cycles = parts[i++].toLong()
PC = parts[i++].toInt()
SP = parts[i++].toInt()
Expand All @@ -595,6 +598,7 @@ internal class CPU(
N = parts[i++].toInt()
interrupt = parts[i++].toInt()
stall = parts[i].toInt()
println("CPU state restored")
}

private inline fun pagesDiffer(a: Int, b: Int) =
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/kotlin/com/felipecsl/knes/Console.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,20 @@ internal class Console(
val cpuState = cpu.dumpState()
val ppuState = ppu.dumpState()
val apuState = apu.dumpState()
val mapperState = mapper.dumpState()
return mapOf(
"cpu" to cpuState,
"ppu" to ppuState,
"apu" to apuState
"apu" to apuState,
"mapper" to mapperState
)
}

fun restoreState(state: Map<String, *>) {
cpu.restoreState(state["cpu"] as String)
ppu.restoreState(state["ppu"] as String)
apu.restoreState(state["apu"] as String)
mapper.restoreState(state["mapper"] as String)
}

companion object {
Expand Down
15 changes: 10 additions & 5 deletions common/src/main/kotlin/com/felipecsl/knes/Director.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@ class Director(
while (isRunning) {
totalCycles += console.step()
if (totalCycles >= FREQUENCY) {
val currentTime = currentTimeMs()
val msSpent = currentTime - startTime
val clock = (totalCycles * 1000) / msSpent
val speed = clock / FREQUENCY.toFloat()
println("Clock=${clock}Hz (${speed}x)")
val currentTime = trackConsoleSpeed(startTime, totalCycles)
totalCycles = 0
startTime = currentTime
}
}
}

private fun trackConsoleSpeed(startTime: Long, totalCycles: Long): Long {
val currentTime = currentTimeMs()
val msSpent = currentTime - startTime
val clock = (totalCycles * 1000) / msSpent
val speed = clock / FREQUENCY.toFloat()
println("Clock=${clock}Hz (${speed}x)")
return currentTime
}

fun setButtons1(buttons: BooleanArray) {
console.setButtons(buttons)
}
Expand Down
8 changes: 8 additions & 0 deletions common/src/main/kotlin/com/felipecsl/knes/MMC1.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ internal class MMC1(
}
}

override fun restoreState(state: String) {
TODO("not implemented")
}

override fun dumpState(): String {
TODO("not implemented")
}

private fun writeControl(value: Int) {
control = value
chrMode = (value shr 4) and 1 and 0xFF
Expand Down
31 changes: 31 additions & 0 deletions common/src/main/kotlin/com/felipecsl/knes/MMC3.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,37 @@ internal class MMC3(
}
}

override fun restoreState(state: String) {
val parts = state.split("\n")
var i = 0
register = parts[i++].toInt()
registers = parts[i++].toIntArray()
prgMode = parts[i++].toInt()
chrMode = parts[i++].toInt()
prgOffsets = parts[i++].toIntArray()
chrOffsets = parts[i++].toIntArray()
reload = parts[i++].toInt()
counter = parts[i++].toInt()
irqEnable = parts[i].toBoolean()
println("MMC3 state restored")
}

override fun dumpState(): String {
return listOf(
register,
registers.joinToString(),
prgMode,
chrMode,
prgOffsets.joinToString(),
chrOffsets.joinToString(),
reload,
counter,
irqEnable
).joinToString("\n").also {
println("MMC3 state saved")
}
}

override fun write(address: Int, value: Int) {
when {
address < 0x2000 -> {
Expand Down
2 changes: 2 additions & 0 deletions common/src/main/kotlin/com/felipecsl/knes/Mapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ internal interface Mapper {
fun read(address: Int): Int
fun write(address: Int, value: Int)
fun step()
fun restoreState(state: String)
fun dumpState(): String

companion object {
fun newMapper(cartridge: Cartridge, stepCallback: MapperStepCallback?): Mapper =
Expand Down
20 changes: 11 additions & 9 deletions common/src/main/kotlin/com/felipecsl/knes/PPU.kt
Original file line number Diff line number Diff line change
Expand Up @@ -195,19 +195,20 @@ internal class PPU(
flagSpriteTable, flagBackgroundTable, flagSpriteSize, flagMasterSlave, flagGrayscale,
flagShowLeftBackground, flagShowLeftSprites, flagShowBackground, flagShowSprites,
flagRedTint, flagGreenTint, flagBlueTint, flagSpriteZeroHit, flagSpriteOverflow,
oamAddress, bufferedData).joinToString("\n")
oamAddress, bufferedData).joinToString("\n").also {
println("PPU state saved")
}
}

fun restoreState(state: String) {
val parts = state.split("\n")
val stringToIntArray = { s: String -> s.split(", ").map { it.toInt() }.toIntArray() }
var i = 0
cycle = parts[i++].toInt()
scanLine = parts[i++].toInt()
frame = parts[i++].toInt()
paletteData = stringToIntArray(parts[i++])
nameTableData = stringToIntArray(parts[i++])
oamData = stringToIntArray(parts[i++])
paletteData = parts[i++].toIntArray()
nameTableData = parts[i++].toIntArray()
oamData = parts[i++].toIntArray()
v = parts[i++].toInt()
t = parts[i++].toInt()
x = parts[i++].toInt()
Expand All @@ -224,10 +225,10 @@ internal class PPU(
highTileByte = parts[i++].toInt()
tileData = parts[i++].toLong()
spriteCount = parts[i++].toInt()
spritePatterns = stringToIntArray(parts[i++])
spritePositions = stringToIntArray(parts[i++])
spritePriorities = stringToIntArray(parts[i++])
spriteIndexes = stringToIntArray(parts[i++])
spritePatterns = parts[i++].toIntArray()
spritePositions = parts[i++].toIntArray()
spritePriorities = parts[i++].toIntArray()
spriteIndexes = parts[i++].toIntArray()
flagNameTable = parts[i++].toInt()
flagIncrement = parts[i++].toInt()
flagSpriteTable = parts[i++].toInt()
Expand All @@ -246,6 +247,7 @@ internal class PPU(
flagSpriteOverflow = parts[i++].toInt()
oamAddress = parts[i++].toInt()
bufferedData = parts[i].toInt()
println("PPU state restored")
}

fun step(): Boolean {
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/kotlin/com/felipecsl/knes/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ fun Int.ensurePositive(): Int {
throw RuntimeException("Value is not positive: $this")
}
return this
}
}

fun String.toIntArray() =
split(", ").map(String::toInt).toIntArray()

0 comments on commit ee2b165

Please sign in to comment.