mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-09-14 22:36:01 -05:00
Make NetplaySession not a singleton
Create a new NetplaySession each time we try to join a netplay game. Hold onto it in NetplayManager so its available to the different activities that need to access it. Close the session when backing out of the netplay UI. Some guardrails in case things go out of sync: creating a session closes the old one if it is still around for some reason, finalizer in NetplaySession to release native resources if not closed explicitly for some reason. Profiling done to ensure all kotlin and native objects are successfully cleared / garbage collected.
This commit is contained in:
@@ -346,7 +346,7 @@ object NativeLibrary {
|
||||
* Begins emulation for a netplay session, using the BootSessionData provided by the host.
|
||||
*/
|
||||
@JvmStatic
|
||||
external fun RunNetPlay(paths: Array<String>, riivolution: Boolean)
|
||||
external fun RunNetPlay(paths: Array<String>, riivolution: Boolean, bootSessionDataPointer: Long)
|
||||
|
||||
@JvmStatic
|
||||
external fun ChangeDisc(path: String)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2003 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.netplay
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
object NetplayManager {
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
@Volatile
|
||||
private var closeComplete: CompletableDeferred<Unit>? = null
|
||||
|
||||
@Volatile
|
||||
var activeSession: NetplaySession? = null
|
||||
private set
|
||||
|
||||
suspend fun createSession(): NetplaySession = mutex.withLock {
|
||||
closeComplete?.await()
|
||||
|
||||
// Sessions should be closed by UI navigation, but just in case.
|
||||
activeSession?.closeBlocking()
|
||||
|
||||
closeComplete = CompletableDeferred()
|
||||
|
||||
NetplaySession(
|
||||
onClosed = {
|
||||
activeSession = null
|
||||
closeComplete?.complete(Unit)
|
||||
}
|
||||
).also {
|
||||
activeSession = it
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ package org.dolphinemu.dolphinemu.features.netplay
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
@@ -29,14 +28,21 @@ import org.dolphinemu.dolphinemu.features.netplay.model.NetplayMessage
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.Player
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.SaveTransferProgress
|
||||
|
||||
object Netplay {
|
||||
@Keep
|
||||
class NetplaySession(
|
||||
private val onClosed: (NetplaySession) -> Unit,
|
||||
) {
|
||||
|
||||
private var netPlayUICallbacksPointer: Long = nativeCreateUICallbacks()
|
||||
|
||||
private var netPlayClientPointer: Long = 0
|
||||
|
||||
@Keep
|
||||
private var bootSessionDataPointer: Long = 0
|
||||
|
||||
private var sessionScope: CoroutineScope? = null
|
||||
private val sessionScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
@Volatile
|
||||
var isClosed = false
|
||||
private set
|
||||
|
||||
val isLaunching: Boolean
|
||||
get() = bootSessionDataPointer != 0L
|
||||
@@ -93,85 +99,49 @@ object Netplay {
|
||||
val saveTransferProgress = _saveTransferProgress.asStateFlow()
|
||||
|
||||
suspend fun join(): Boolean = withContext(Dispatchers.IO) {
|
||||
val scope = createSessionScope()
|
||||
if (isClosed) throw IllegalStateException("Cannot join a closed session")
|
||||
|
||||
// Gather all messages that should appear in the chat window.
|
||||
mergeMessages()
|
||||
.runningFold(emptyList<NetplayMessage>()) { acc, msg -> listOf(msg) + acc }
|
||||
.onEach { _messages.tryEmit(it) }
|
||||
.launchIn(scope)
|
||||
.launchIn(sessionScope)
|
||||
|
||||
netPlayClientPointer = Join()
|
||||
val isConnected = netPlayClientPointer != 0L && isClientConnected()
|
||||
netPlayClientPointer = nativeJoin()
|
||||
|
||||
if (!isActive) {
|
||||
releaseNetplayClient()
|
||||
if (netPlayClientPointer == 0L || !isActive) {
|
||||
closeBlocking()
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
if (isConnected) {
|
||||
return@withContext true
|
||||
}
|
||||
|
||||
releaseNetplayClient()
|
||||
false
|
||||
true
|
||||
}
|
||||
|
||||
suspend fun quit() = withContext(Dispatchers.IO) {
|
||||
releaseNetplayClient()
|
||||
}
|
||||
fun sendMessage(message: String) = nativeSendMessage(message)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun releaseNetplayClient() {
|
||||
sessionScope?.cancel()
|
||||
sessionScope = null
|
||||
fun adjustPadBufferSize(buffer: Int) = nativeAdjustPadBufferSize(buffer)
|
||||
|
||||
if (bootSessionDataPointer != 0L) {
|
||||
ReleaseBootSessionData()
|
||||
fun consumeBootSessionData(): Long {
|
||||
return bootSessionDataPointer.also {
|
||||
bootSessionDataPointer = 0
|
||||
}
|
||||
|
||||
if (netPlayClientPointer != 0L) {
|
||||
ReleaseNetplayClient()
|
||||
netPlayClientPointer = 0
|
||||
}
|
||||
|
||||
_launchGame.flush()
|
||||
_stopGame.flush()
|
||||
_connectionErrors.flush()
|
||||
_players.resetReplayCache()
|
||||
_messages.resetReplayCache()
|
||||
_chatMessages.resetReplayCache()
|
||||
_game.resetReplayCache()
|
||||
_hostInputAuthorityEnabled.resetReplayCache()
|
||||
_padBuffer.resetReplayCache()
|
||||
_saveTransferProgress.value = null
|
||||
}
|
||||
|
||||
private fun createSessionScope(): CoroutineScope {
|
||||
sessionScope?.cancel()
|
||||
return CoroutineScope(SupervisorJob() + Dispatchers.IO).also {
|
||||
sessionScope = it
|
||||
}
|
||||
suspend fun close() = withContext(Dispatchers.IO) {
|
||||
closeBlocking()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private external fun Join(): Long
|
||||
@Synchronized
|
||||
fun closeBlocking() {
|
||||
if (isClosed) return
|
||||
isClosed = true
|
||||
sessionScope.cancel()
|
||||
releaseNativeResources()
|
||||
onClosed(this)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
external fun isClientConnected(): Boolean
|
||||
|
||||
@JvmStatic
|
||||
external fun sendMessage(message: String)
|
||||
|
||||
@JvmStatic
|
||||
external fun adjustPadBufferSize(buffer: Int)
|
||||
|
||||
@JvmStatic
|
||||
private external fun ReleaseBootSessionData()
|
||||
|
||||
@JvmStatic
|
||||
private external fun ReleaseNetplayClient()
|
||||
protected fun finalize() {
|
||||
releaseNativeResources()
|
||||
}
|
||||
|
||||
private fun mergeMessages(): Flow<NetplayMessage> = merge(
|
||||
chatMessages.map { NetplayMessage.Chat(it) },
|
||||
@@ -180,10 +150,45 @@ object Netplay {
|
||||
padBuffer.map { NetplayMessage.BufferChanged(it) },
|
||||
)
|
||||
|
||||
private fun releaseNativeResources() {
|
||||
val currentBootSessionDataPointer = bootSessionDataPointer
|
||||
if (currentBootSessionDataPointer != 0L) {
|
||||
bootSessionDataPointer = 0
|
||||
nativeReleaseBootSessionData(currentBootSessionDataPointer)
|
||||
}
|
||||
|
||||
val currentNetPlayClientPointer = netPlayClientPointer
|
||||
if (currentNetPlayClientPointer != 0L) {
|
||||
netPlayClientPointer = 0
|
||||
nativeReleaseClient(currentNetPlayClientPointer)
|
||||
}
|
||||
|
||||
val currentNetPlayUICallbacksPointer = netPlayUICallbacksPointer
|
||||
if (currentNetPlayUICallbacksPointer != 0L) {
|
||||
netPlayUICallbacksPointer = 0
|
||||
nativeReleaseUICallbacks(currentNetPlayUICallbacksPointer)
|
||||
}
|
||||
}
|
||||
|
||||
// JNI methods
|
||||
|
||||
private external fun nativeCreateUICallbacks(): Long
|
||||
|
||||
private external fun nativeJoin(): Long
|
||||
|
||||
private external fun nativeSendMessage(message: String)
|
||||
|
||||
private external fun nativeAdjustPadBufferSize(buffer: Int)
|
||||
|
||||
private external fun nativeReleaseUICallbacks(pointer: Long)
|
||||
|
||||
private external fun nativeReleaseClient(pointer: Long)
|
||||
|
||||
private external fun nativeReleaseBootSessionData(pointer: Long)
|
||||
|
||||
// NetPlayUI callbacks
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onBootGame(gameFilePath: String, bootSessionDataPointer: Long) {
|
||||
this.bootSessionDataPointer = bootSessionDataPointer
|
||||
_stopGame.flush()
|
||||
@@ -191,57 +196,47 @@ object Netplay {
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onStopGame() {
|
||||
_stopGame.trySend(Unit)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onConnectionLost() {
|
||||
_connectionLost.trySend(Unit)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onConnectionError(message: String) {
|
||||
_connectionErrors.trySend(message)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onUpdate(players: Array<Player>) {
|
||||
_players.tryEmit(players.toList())
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onChatMessageReceived(message: String) {
|
||||
_chatMessages.tryEmit(message)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onHostInputAuthorityChanged(enabled: Boolean) {
|
||||
_hostInputAuthorityEnabled.tryEmit(enabled)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onGameChanged(game: String) {
|
||||
_game.tryEmit(game)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onPadBufferChanged(buffer: Int) {
|
||||
// Only for remote pad buffer settings. Ignore local max buffer changes.
|
||||
if (_hostInputAuthorityEnabled.replayCache.firstOrNull() == true) return
|
||||
_padBuffer.tryEmit(buffer)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onShowChunkedProgressDialog(title: String, dataSize: Long, playerIds: IntArray) {
|
||||
val players = _players.replayCache.firstOrNull()
|
||||
_saveTransferProgress.value = SaveTransferProgress(
|
||||
@@ -258,7 +253,6 @@ object Netplay {
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onSetChunkedProgress(playerId: Int, progress: Long) {
|
||||
val current = _saveTransferProgress.value
|
||||
_saveTransferProgress.value = current?.copy(
|
||||
@@ -273,11 +267,9 @@ object Netplay {
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onHideChunkedProgressDialog() {
|
||||
_saveTransferProgress.value = null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun <T> Channel<T>.flush() {
|
||||
@@ -3,22 +3,31 @@
|
||||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.dolphinemu.dolphinemu.features.netplay.Netplay
|
||||
import org.dolphinemu.dolphinemu.features.netplay.NetplayManager
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
|
||||
import org.dolphinemu.dolphinemu.services.GameFileCacheManager
|
||||
|
||||
class NetplaySetupViewModel : ViewModel() {
|
||||
class NetplaySetupViewModel(
|
||||
private val netplayManager: NetplayManager,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _connectionRole = MutableStateFlow<ConnectionRole>(ConnectionRole.Connect)
|
||||
val connectionRole = _connectionRole.asStateFlow()
|
||||
|
||||
@@ -45,7 +54,8 @@ class NetplaySetupViewModel : ViewModel() {
|
||||
private val _connecting = MutableStateFlow(false)
|
||||
val connecting = _connecting.asStateFlow()
|
||||
|
||||
val errors = Netplay.connectionErrors
|
||||
private val _errors = MutableSharedFlow<String>(extraBufferCapacity = 8)
|
||||
val errors = _errors.asSharedFlow()
|
||||
|
||||
init {
|
||||
GameFileCacheManager.startLoad()
|
||||
@@ -89,16 +99,42 @@ class NetplaySetupViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun connect() {
|
||||
if (_connecting.value) return
|
||||
|
||||
_connecting.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
GameFileCacheManager.isLoading().asFlow().first { it == false }
|
||||
var errorForwarding: Job? = null
|
||||
|
||||
if (Netplay.join()) {
|
||||
_showNetplayScreen.trySend(Unit)
|
||||
try {
|
||||
GameFileCacheManager.isLoading().asFlow().first { it == false }
|
||||
|
||||
val session = netplayManager.createSession()
|
||||
errorForwarding = session.connectionErrors
|
||||
.onEach { _errors.emit(it) }
|
||||
.launchIn(this)
|
||||
|
||||
if (session.join()) {
|
||||
_showNetplayScreen.trySend(Unit)
|
||||
}
|
||||
} finally {
|
||||
errorForwarding?.cancel()
|
||||
_connecting.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_connecting.value = false
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// There should not be an active session at this point but in case one was created
|
||||
// but launching the Netplay screen failed, close it.
|
||||
netplayManager.activeSession?.closeBlocking()
|
||||
}
|
||||
|
||||
class Factory(private val netplayManager: NetplayManager) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return NetplaySetupViewModel(netplayManager) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,51 +3,43 @@
|
||||
package org.dolphinemu.dolphinemu.features.netplay.model
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.dolphinemu.dolphinemu.features.netplay.Netplay
|
||||
import org.dolphinemu.dolphinemu.features.netplay.NetplaySession
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
|
||||
|
||||
class NetplayViewModel : ViewModel() {
|
||||
val launchGame = Netplay.launchGame
|
||||
class NetplayViewModel(
|
||||
private val netplaySession: NetplaySession,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _goBack = Channel<Unit>(CONFLATED)
|
||||
val goBack = _goBack.receiveAsFlow()
|
||||
val launchGame = netplaySession.launchGame
|
||||
|
||||
val connectionLost = Netplay.connectionLost
|
||||
val connectionLost = netplaySession.connectionLost
|
||||
|
||||
val players = Netplay.players
|
||||
val players = netplaySession.players
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||
|
||||
val messages = Netplay.messages
|
||||
val messages = netplaySession.messages
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||
|
||||
val game = Netplay.game
|
||||
val game = netplaySession.game
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||
|
||||
val hostInputAuthority = Netplay.hostInputAuthorityEnabled
|
||||
val hostInputAuthority = netplaySession.hostInputAuthorityEnabled
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
|
||||
|
||||
private val _maxBuffer = MutableStateFlow(IntSetting.NETPLAY_CLIENT_BUFFER_SIZE.int)
|
||||
val maxBuffer = _maxBuffer.asStateFlow()
|
||||
|
||||
val saveTransferProgress = Netplay.saveTransferProgress
|
||||
|
||||
init {
|
||||
if (!Netplay.isClientConnected()) {
|
||||
_goBack.trySend(Unit)
|
||||
}
|
||||
}
|
||||
val saveTransferProgress = netplaySession.saveTransferProgress
|
||||
|
||||
fun sendMessage(message: String) {
|
||||
val trimmedMessage = message.trim()
|
||||
@@ -55,20 +47,29 @@ class NetplayViewModel : ViewModel() {
|
||||
return
|
||||
}
|
||||
|
||||
Netplay.sendMessage(trimmedMessage)
|
||||
netplaySession.sendMessage(trimmedMessage)
|
||||
}
|
||||
|
||||
fun setMaxBuffer(buffer: Int) {
|
||||
_maxBuffer.value = buffer
|
||||
IntSetting.NETPLAY_CLIENT_BUFFER_SIZE.setInt(NativeConfig.LAYER_BASE, buffer)
|
||||
Netplay.adjustPadBufferSize(buffer)
|
||||
netplaySession.adjustPadBufferSize(buffer)
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
// Closing the netplay session is a bit slow for the main thread so launch in
|
||||
// GlobalScope and allow the activity and view model to finish immediately.
|
||||
GlobalScope.launch {
|
||||
Netplay.quit()
|
||||
netplaySession.close()
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(private val session: NetplaySession) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return NetplayViewModel(session) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity
|
||||
import org.dolphinemu.dolphinemu.features.netplay.NetplayManager
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.NetplayViewModel
|
||||
import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
|
||||
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
|
||||
@@ -29,11 +30,13 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val viewModel = ViewModelProvider(this)[NetplayViewModel::class.java]
|
||||
val session = NetplayManager.activeSession
|
||||
if (session == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.goBack
|
||||
.onEach { finish() }
|
||||
.launchIn(lifecycleScope)
|
||||
val viewModel = ViewModelProvider(this, NetplayViewModel.Factory(session))[NetplayViewModel::class.java]
|
||||
|
||||
viewModel.launchGame
|
||||
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.dolphinemu.dolphinemu.features.netplay.NetplayManager
|
||||
import org.dolphinemu.dolphinemu.features.netplay.model.NetplaySetupViewModel
|
||||
import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
|
||||
import org.dolphinemu.dolphinemu.ui.theme.DolphinTheme
|
||||
@@ -28,7 +29,10 @@ class NetplaySetupActivity : AppCompatActivity(), ThemeProvider {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val viewModel = ViewModelProvider(this)[NetplaySetupViewModel::class.java]
|
||||
val viewModel = ViewModelProvider(
|
||||
this,
|
||||
NetplaySetupViewModel.Factory(NetplayManager)
|
||||
)[NetplaySetupViewModel::class.java]
|
||||
|
||||
viewModel.showNetplayScreen
|
||||
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
|
||||
|
||||
@@ -16,7 +16,7 @@ import kotlinx.coroutines.launch
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary
|
||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity
|
||||
import org.dolphinemu.dolphinemu.databinding.FragmentEmulationBinding
|
||||
import org.dolphinemu.dolphinemu.features.netplay.Netplay
|
||||
import org.dolphinemu.dolphinemu.features.netplay.NetplayManager
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
import org.dolphinemu.dolphinemu.overlay.InputOverlay
|
||||
@@ -211,7 +211,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
// Don't load temporary saves when launching Netplay, this path can trigger
|
||||
// when a game starts due to orientation changes caused by a mismatch in menu
|
||||
// vs emulation activity orientations.
|
||||
if (loadPreviousTemporaryState && !Netplay.isLaunching) {
|
||||
val netplaySession = NetplayManager.activeSession
|
||||
if (loadPreviousTemporaryState && netplaySession?.isLaunching != true) {
|
||||
Log.debug("[EmulationFragment] Starting emulation thread from previous state.")
|
||||
val paths = requireNotNull(gamePaths) {
|
||||
"Cannot start emulation without any game paths"
|
||||
@@ -221,16 +222,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
if (launchSystemMenu) {
|
||||
Log.debug("[EmulationFragment] Starting emulation thread for the Wii Menu.")
|
||||
NativeLibrary.RunSystemMenu()
|
||||
} else if (Netplay.isLaunching) {
|
||||
} else if (netplaySession?.isLaunching == true) {
|
||||
Log.debug("[EmulationFragment] Starting emulation thread for Netplay.")
|
||||
val paths = requireNotNull(gamePaths) {
|
||||
"Cannot start emulation without any game paths"
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
Netplay.stopGame.first()
|
||||
netplaySession.stopGame.first()
|
||||
stopEmulation()
|
||||
}
|
||||
NativeLibrary.RunNetPlay(paths, riivolution)
|
||||
NativeLibrary.RunNetPlay(
|
||||
paths,
|
||||
riivolution,
|
||||
netplaySession.consumeBootSessionData()
|
||||
)
|
||||
} else {
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.")
|
||||
val paths = requireNotNull(gamePaths) {
|
||||
|
||||
Reference in New Issue
Block a user