mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-05-24 15:37:05 +02:00
added proper intent game launch swap support
This commit is contained in:
parent
844e0360c7
commit
883e750134
3 changed files with 308 additions and 29 deletions
|
|
@ -25,6 +25,11 @@ import android.hardware.SensorEventListener
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.navigation.NavOptions
|
||||||
|
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||||
|
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
||||||
import android.util.Rational
|
import android.util.Rational
|
||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
|
@ -87,6 +92,28 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
private val emulationViewModel: EmulationViewModel by viewModels()
|
private val emulationViewModel: EmulationViewModel by viewModels()
|
||||||
|
|
||||||
private var foregroundService: Intent? = null
|
private var foregroundService: Intent? = null
|
||||||
|
private val mainHandler = Handler(Looper.getMainLooper())
|
||||||
|
private var pendingRomSwapIntent: Intent? = null
|
||||||
|
private var isWaitingForRomSwapStop = false
|
||||||
|
private var romSwapNativeStopped = false
|
||||||
|
private var romSwapThreadStopped = false
|
||||||
|
private var romSwapGeneration = 0
|
||||||
|
private var hasEmulationSession = processHasEmulationSession
|
||||||
|
private val romSwapStopTimeoutRunnable = Runnable { onRomSwapStopTimeout() }
|
||||||
|
|
||||||
|
private fun onRomSwapStopTimeout() {
|
||||||
|
if (!isWaitingForRomSwapStop) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log.warning("[EmulationActivity] ROM swap stop timed out; retrying native stop and continuing to wait")
|
||||||
|
NativeLibrary.stopEmulation()
|
||||||
|
scheduleRomSwapStopTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleRomSwapStopTimeout() {
|
||||||
|
mainHandler.removeCallbacks(romSwapStopTimeoutRunnable)
|
||||||
|
mainHandler.postDelayed(romSwapStopTimeoutRunnable, ROM_SWAP_STOP_TIMEOUT_MS)
|
||||||
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
super.attachBaseContext(YuzuApplication.applyLanguage(base))
|
super.attachBaseContext(YuzuApplication.applyLanguage(base))
|
||||||
|
|
@ -128,9 +155,29 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
val launchIntent = Intent(intent)
|
||||||
|
val shouldDeferLaunchForSwap = hasEmulationSession && isSwapIntent(launchIntent)
|
||||||
|
if (shouldDeferLaunchForSwap) {
|
||||||
|
Log.info("[EmulationActivity] onCreate detected existing session; deferring new game setup for swap")
|
||||||
|
emulationViewModel.setIsEmulationStopping(true)
|
||||||
|
emulationViewModel.setEmulationStopped(false)
|
||||||
|
}
|
||||||
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
|
val initialArgs = if (shouldDeferLaunchForSwap) {
|
||||||
|
Bundle(intent.extras ?: Bundle()).apply {
|
||||||
|
processSessionGame?.let { putParcelable("game", it) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
intent.extras
|
||||||
|
}
|
||||||
|
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, initialArgs)
|
||||||
|
if (shouldDeferLaunchForSwap) {
|
||||||
|
mainHandler.post {
|
||||||
|
handleSwapIntent(launchIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isActivityRecreated = savedInstanceState != null
|
isActivityRecreated = savedInstanceState != null
|
||||||
|
|
||||||
|
|
@ -210,6 +257,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
mainHandler.removeCallbacks(romSwapStopTimeoutRunnable)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
inputManager.unregisterInputDeviceListener(this)
|
inputManager.unregisterInputDeviceListener(this)
|
||||||
stopForegroundService(this)
|
stopForegroundService(this)
|
||||||
|
|
@ -228,17 +276,123 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setIntent(intent)
|
handleSwapIntent(intent)
|
||||||
|
|
||||||
// Reset navigation graph with new intent data to recreate EmulationFragment
|
|
||||||
val navHostFragment =
|
|
||||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
|
||||||
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
|
|
||||||
|
|
||||||
nfcReader.onNewIntent(intent)
|
nfcReader.onNewIntent(intent)
|
||||||
InputHandler.updateControllerData()
|
InputHandler.updateControllerData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isSwapIntent(intent: Intent): Boolean {
|
||||||
|
return when {
|
||||||
|
intent.getBooleanExtra(EXTRA_OVERLAY_GAMELESS_EDIT_MODE, false) -> false
|
||||||
|
intent.action == CustomSettingsHandler.CUSTOM_CONFIG_ACTION -> true
|
||||||
|
intent.data != null -> true
|
||||||
|
else -> {
|
||||||
|
val extras = intent.extras
|
||||||
|
extras != null &&
|
||||||
|
BundleCompat.getParcelable(extras, EXTRA_SELECTED_GAME, Game::class.java) != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSwapIntent(intent: Intent) {
|
||||||
|
if (!isSwapIntent(intent)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingRomSwapIntent = Intent(intent)
|
||||||
|
|
||||||
|
if (!isWaitingForRomSwapStop) {
|
||||||
|
Log.info("[EmulationActivity] Begin ROM swap: data=${intent.data}")
|
||||||
|
isWaitingForRomSwapStop = true
|
||||||
|
romSwapNativeStopped = false
|
||||||
|
romSwapThreadStopped = false
|
||||||
|
romSwapGeneration += 1
|
||||||
|
val thisSwapGeneration = romSwapGeneration
|
||||||
|
emulationViewModel.setIsEmulationStopping(true)
|
||||||
|
emulationViewModel.setEmulationStopped(false)
|
||||||
|
val navHostFragment =
|
||||||
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||||
|
val childFragmentManager = navHostFragment?.childFragmentManager
|
||||||
|
val stoppingFragmentForSwap =
|
||||||
|
(childFragmentManager?.primaryNavigationFragment as? EmulationFragment) ?:
|
||||||
|
childFragmentManager
|
||||||
|
?.fragments
|
||||||
|
?.asReversed()
|
||||||
|
?.firstOrNull {
|
||||||
|
it is EmulationFragment &&
|
||||||
|
it.isAdded &&
|
||||||
|
it.view != null &&
|
||||||
|
!it.isRemoving
|
||||||
|
} as? EmulationFragment
|
||||||
|
|
||||||
|
val hasSessionForSwap = hasEmulationSession || stoppingFragmentForSwap != null
|
||||||
|
|
||||||
|
if (!hasSessionForSwap) {
|
||||||
|
romSwapNativeStopped = true
|
||||||
|
romSwapThreadStopped = true
|
||||||
|
} else {
|
||||||
|
if (stoppingFragmentForSwap != null) {
|
||||||
|
stoppingFragmentForSwap.stopForRomSwap()
|
||||||
|
stoppingFragmentForSwap.notifyWhenEmulationThreadStops {
|
||||||
|
if (!isWaitingForRomSwapStop || romSwapGeneration != thisSwapGeneration) {
|
||||||
|
return@notifyWhenEmulationThreadStops
|
||||||
|
}
|
||||||
|
romSwapThreadStopped = true
|
||||||
|
Log.info("[EmulationActivity] ROM swap thread stop acknowledged")
|
||||||
|
launchPendingRomSwap(force = false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.warning("[EmulationActivity] ROM swap stop target fragment not found; requesting native stop")
|
||||||
|
romSwapThreadStopped = true
|
||||||
|
NativeLibrary.stopEmulation()
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleRomSwapStopTimeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launchPendingRomSwap(force = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchPendingRomSwap(force: Boolean) {
|
||||||
|
if (!isWaitingForRomSwapStop) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!force && (!romSwapNativeStopped || !romSwapThreadStopped)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val swapIntent = pendingRomSwapIntent ?: return
|
||||||
|
Log.info("[EmulationActivity] Launching pending ROM swap: data=${swapIntent.data}")
|
||||||
|
pendingRomSwapIntent = null
|
||||||
|
isWaitingForRomSwapStop = false
|
||||||
|
romSwapNativeStopped = false
|
||||||
|
romSwapThreadStopped = false
|
||||||
|
mainHandler.removeCallbacks(romSwapStopTimeoutRunnable)
|
||||||
|
applyGameLaunchIntent(swapIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyGameLaunchIntent(intent: Intent) {
|
||||||
|
hasEmulationSession = true
|
||||||
|
processHasEmulationSession = true
|
||||||
|
emulationViewModel.setIsEmulationStopping(false)
|
||||||
|
emulationViewModel.setEmulationStopped(false)
|
||||||
|
setIntent(Intent(intent))
|
||||||
|
val navHostFragment =
|
||||||
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
|
val navController = navHostFragment.navController
|
||||||
|
val startArgs = intent.extras?.let { Bundle(it) } ?: Bundle()
|
||||||
|
val navOptions = NavOptions.Builder()
|
||||||
|
.setPopUpTo(R.id.emulationFragment, true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
navController.navigate(R.id.emulationFragment, startArgs, navOptions)
|
||||||
|
}.onFailure {
|
||||||
|
Log.warning("[EmulationActivity] ROM swap navigate fallback to setGraph: ${it.message}")
|
||||||
|
navController.setGraph(R.navigation.emulation_navigation, startArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
|
||||||
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
|
if (event.keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
|
||||||
|
|
@ -608,19 +762,48 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEmulationStarted() {
|
fun onEmulationStarted() {
|
||||||
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
mainHandler.post { onEmulationStarted() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hasEmulationSession = true
|
||||||
|
processHasEmulationSession = true
|
||||||
emulationViewModel.setEmulationStarted(true)
|
emulationViewModel.setEmulationStarted(true)
|
||||||
|
emulationViewModel.setIsEmulationStopping(false)
|
||||||
|
emulationViewModel.setEmulationStopped(false)
|
||||||
NativeLibrary.playTimeManagerStart()
|
NativeLibrary.playTimeManagerStart()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEmulationStopped(status: Int) {
|
fun onEmulationStopped(status: Int) {
|
||||||
if (status == 0 && emulationViewModel.programChanged.value == -1) {
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
mainHandler.post { onEmulationStopped(status) }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hasEmulationSession = false
|
||||||
|
processHasEmulationSession = false
|
||||||
|
if (isWaitingForRomSwapStop) {
|
||||||
|
romSwapNativeStopped = true
|
||||||
|
Log.info("[EmulationActivity] ROM swap native stop acknowledged")
|
||||||
|
launchPendingRomSwap(force = false)
|
||||||
|
} else if (status == 0 && emulationViewModel.programChanged.value == -1) {
|
||||||
|
processSessionGame = null
|
||||||
finish()
|
finish()
|
||||||
|
} else if (!isWaitingForRomSwapStop) {
|
||||||
|
processSessionGame = null
|
||||||
}
|
}
|
||||||
emulationViewModel.setEmulationStopped(true)
|
emulationViewModel.setEmulationStopped(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSessionGame(game: Game?) {
|
||||||
|
processSessionGame = game
|
||||||
|
}
|
||||||
|
|
||||||
fun onProgramChanged(programIndex: Int) {
|
fun onProgramChanged(programIndex: Int) {
|
||||||
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
mainHandler.post { onProgramChanged(programIndex) }
|
||||||
|
return
|
||||||
|
}
|
||||||
emulationViewModel.setProgramChanged(programIndex)
|
emulationViewModel.setProgramChanged(programIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -644,6 +827,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
||||||
const val EXTRA_OVERLAY_GAMELESS_EDIT_MODE = "overlayGamelessEditMode"
|
const val EXTRA_OVERLAY_GAMELESS_EDIT_MODE = "overlayGamelessEditMode"
|
||||||
|
private const val ROM_SWAP_STOP_TIMEOUT_MS = 5000L
|
||||||
|
@Volatile
|
||||||
|
private var processHasEmulationSession = false
|
||||||
|
@Volatile
|
||||||
|
private var processSessionGame: Game? = null
|
||||||
|
|
||||||
fun stopForegroundService(activity: Activity) {
|
fun stopForegroundService(activity: Activity) {
|
||||||
val startIntent = Intent(activity, ForegroundService::class.java)
|
val startIntent = Intent(activity, ForegroundService::class.java)
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.window.layout.FoldingFeature
|
import androidx.window.layout.FoldingFeature
|
||||||
import androidx.window.layout.WindowInfoTracker
|
import androidx.window.layout.WindowInfoTracker
|
||||||
|
|
@ -135,6 +136,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
private var intentGame: Game? = null
|
private var intentGame: Game? = null
|
||||||
private var isCustomSettingsIntent = false
|
private var isCustomSettingsIntent = false
|
||||||
|
private var isStoppingForRomSwap = false
|
||||||
|
private var deferGameSetupUntilStopCompletes = false
|
||||||
|
|
||||||
private var perfStatsRunnable: Runnable? = null
|
private var perfStatsRunnable: Runnable? = null
|
||||||
private var socRunnable: Runnable? = null
|
private var socRunnable: Runnable? = null
|
||||||
|
|
@ -238,6 +241,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emulationViewModel.isEmulationStopping.value) {
|
||||||
|
deferGameSetupUntilStopCompletes = true
|
||||||
|
if (game == null) {
|
||||||
|
game = args.game ?: intentGame
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
finishGameSetup()
|
finishGameSetup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,6 +271,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
game = gameToUse
|
game = gameToUse
|
||||||
|
emulationActivity?.updateSessionGame(gameToUse)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.error("[EmulationFragment] Error during game setup: ${e.message}")
|
Log.error("[EmulationFragment] Error during game setup: ${e.message}")
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
@ -334,7 +346,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
emulationState = EmulationState(game!!.path) {
|
emulationState = EmulationState(game!!.path) {
|
||||||
return@EmulationState driverViewModel.isInteractionAllowed.value
|
return@EmulationState driverViewModel.isInteractionAllowed.value &&
|
||||||
|
!isStoppingForRomSwap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -890,8 +903,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
GameIconUtils.loadGameIcon(game!!, binding.loadingImage)
|
game?.let {
|
||||||
binding.loadingTitle.text = game!!.title
|
GameIconUtils.loadGameIcon(it, binding.loadingImage)
|
||||||
|
binding.loadingTitle.text = it.title
|
||||||
|
} ?: run {
|
||||||
|
binding.loadingTitle.text = ""
|
||||||
|
}
|
||||||
binding.loadingTitle.isSelected = true
|
binding.loadingTitle.isSelected = true
|
||||||
binding.loadingText.isSelected = true
|
binding.loadingText.isSelected = true
|
||||||
|
|
||||||
|
|
@ -959,6 +976,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
ViewUtils.showView(binding.loadingIndicator)
|
ViewUtils.showView(binding.loadingIndicator)
|
||||||
ViewUtils.hideView(binding.inputContainer)
|
ViewUtils.hideView(binding.inputContainer)
|
||||||
ViewUtils.hideView(binding.showStatsOverlayText)
|
ViewUtils.hideView(binding.showStatsOverlayText)
|
||||||
|
} else if (deferGameSetupUntilStopCompletes) {
|
||||||
|
if (!isAdded) {
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
deferGameSetupUntilStopCompletes = false
|
||||||
|
finishGameSetup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
|
emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
|
||||||
|
|
@ -995,26 +1018,24 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
|
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
|
||||||
if (it && !NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
|
if (it &&
|
||||||
startEmulation()
|
!isStoppingForRomSwap &&
|
||||||
|
!NativeLibrary.isRunning() &&
|
||||||
|
!NativeLibrary.isPaused()
|
||||||
|
) {
|
||||||
|
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||||
|
DirectoryInitialization.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScreenLayout()
|
||||||
|
|
||||||
|
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
driverViewModel.onLaunchGame()
|
driverViewModel.onLaunchGame()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startEmulation(programIndex: Int = 0) {
|
|
||||||
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
|
|
||||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
|
||||||
DirectoryInitialization.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateScreenLayout()
|
|
||||||
|
|
||||||
emulationState.run(emulationActivity!!.isActivityRecreated, programIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
val b = _binding ?: return
|
val b = _binding ?: return
|
||||||
|
|
@ -1375,6 +1396,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
amiiboLoadJob?.cancel()
|
amiiboLoadJob?.cancel()
|
||||||
amiiboLoadJob = null
|
amiiboLoadJob = null
|
||||||
|
perfStatsRunnable?.let { perfStatsUpdateHandler.removeCallbacks(it) }
|
||||||
|
socRunnable?.let { socUpdateHandler.removeCallbacks(it) }
|
||||||
|
handler.removeCallbacksAndMessages(null)
|
||||||
clearPausedFrame()
|
clearPausedFrame()
|
||||||
_binding?.surfaceInputOverlay?.touchEventListener = null
|
_binding?.surfaceInputOverlay?.touchEventListener = null
|
||||||
_binding = null
|
_binding = null
|
||||||
|
|
@ -1382,7 +1406,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetach() {
|
override fun onDetach() {
|
||||||
NativeLibrary.clearEmulationActivity()
|
if (!hasNewerEmulationFragment()) {
|
||||||
|
NativeLibrary.clearEmulationActivity()
|
||||||
|
}
|
||||||
super.onDetach()
|
super.onDetach()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1840,10 +1866,74 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
emulationState.clearSurface()
|
if (this::emulationState.isInitialized && !hasNewerEmulationFragment()) {
|
||||||
|
emulationState.clearSurface()
|
||||||
|
}
|
||||||
emulationStarted = false
|
emulationStarted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hasNewerEmulationFragment(): Boolean {
|
||||||
|
val activity = emulationActivity ?: return false
|
||||||
|
return try {
|
||||||
|
val navHostFragment =
|
||||||
|
activity.supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
|
||||||
|
?: return false
|
||||||
|
val currentFragment = navHostFragment.childFragmentManager.fragments
|
||||||
|
.filterIsInstance<EmulationFragment>()
|
||||||
|
.firstOrNull()
|
||||||
|
currentFragment != null && currentFragment !== this
|
||||||
|
} catch (_: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xbzk: called from EmulationActivity when a new game is loaded while this fragment is still active,
|
||||||
|
// to wait for the emulation thread to stop before allowing the ROM swap to proceed
|
||||||
|
fun notifyWhenEmulationThreadStops(onStopped: () -> Unit) {
|
||||||
|
if (!this::emulationState.isInitialized) {
|
||||||
|
onStopped()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val emuThread = runCatching { emulationState.emulationThread }.getOrNull()
|
||||||
|
if (emuThread == null || !emuThread.isAlive) {
|
||||||
|
onStopped()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Thread({
|
||||||
|
runCatching { emuThread.join() }
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
onStopped()
|
||||||
|
}
|
||||||
|
}, "RomSwapWait").start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// xbzk: called from EmulationActivity when a new game is loaded while this
|
||||||
|
// fragment is still active, to stop the current emulation before swapping the ROM
|
||||||
|
fun stopForRomSwap() {
|
||||||
|
if (isStoppingForRomSwap) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isStoppingForRomSwap = true
|
||||||
|
clearPausedFrame()
|
||||||
|
emulationViewModel.setIsEmulationStopping(true)
|
||||||
|
_binding?.let {
|
||||||
|
binding.loadingText.setText(R.string.shutting_down)
|
||||||
|
ViewUtils.showView(binding.loadingIndicator)
|
||||||
|
ViewUtils.hideView(binding.inputContainer)
|
||||||
|
ViewUtils.hideView(binding.showStatsOverlayText)
|
||||||
|
}
|
||||||
|
if (this::emulationState.isInitialized) {
|
||||||
|
emulationState.stop()
|
||||||
|
if (NativeLibrary.isRunning() || NativeLibrary.isPaused()) {
|
||||||
|
Log.warning("[EmulationFragment] ROM swap stop fallback: forcing native stop request.")
|
||||||
|
NativeLibrary.stopEmulation()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
NativeLibrary.stopEmulation()
|
||||||
|
}
|
||||||
|
NativeConfig.reloadGlobalConfig()
|
||||||
|
}
|
||||||
|
|
||||||
private fun showOverlayOptions() {
|
private fun showOverlayOptions() {
|
||||||
val anchor = binding.inGameMenu.findViewById<View>(R.id.menu_overlay_controls)
|
val anchor = binding.inGameMenu.findViewById<View>(R.id.menu_overlay_controls)
|
||||||
val popup = PopupMenu(requireContext(), anchor)
|
val popup = PopupMenu(requireContext(), anchor)
|
||||||
|
|
@ -2134,6 +2224,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
state = State.STOPPED
|
state = State.STOPPED
|
||||||
} else {
|
} else {
|
||||||
Log.warning("[EmulationFragment] Stop called while already stopped.")
|
Log.warning("[EmulationFragment] Stop called while already stopped.")
|
||||||
|
NativeLibrary.stopEmulation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ object DirectoryInitialization {
|
||||||
fun start() {
|
fun start() {
|
||||||
if (!areDirectoriesReady) {
|
if (!areDirectoriesReady) {
|
||||||
initializeInternalStorage()
|
initializeInternalStorage()
|
||||||
NativeLibrary.initializeSystem(false)
|
|
||||||
NativeConfig.initializeGlobalConfig()
|
NativeConfig.initializeGlobalConfig()
|
||||||
|
NativeLibrary.initializeSystem(false)
|
||||||
NativeLibrary.reloadProfiles()
|
NativeLibrary.reloadProfiles()
|
||||||
migrateSettings()
|
migrateSettings()
|
||||||
areDirectoriesReady = true
|
areDirectoriesReady = true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue