summaryrefslogtreecommitdiff
path: root/Source/Android
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Android')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplayManager.kt38
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt (renamed from Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt)140
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplaySetupViewModel.kt52
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt47
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt11
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt6
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt15
-rw-r--r--Source/Android/jni/AndroidCommon/IDCache.cpp41
-rw-r--r--Source/Android/jni/AndroidCommon/IDCache.h2
-rw-r--r--Source/Android/jni/MainAndroid.cpp7
-rw-r--r--Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp152
-rw-r--r--Source/Android/jni/NetPlay/NetPlayUICallbacks.h8
-rw-r--r--Source/Android/jni/NetPlay/Netplay.cpp92
14 files changed, 402 insertions, 211 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt
index 2e2c295c7d..b52b7b4fa4 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt
@@ -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)
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplayManager.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplayManager.kt
new file mode 100644
index 0000000000..eae44e65be
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplayManager.kt
@@ -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
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
index 1cec94f19d..f47d9ab198 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
@@ -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,97 +99,96 @@ 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
+ true
+ }
+
+ fun sendMessage(message: String) = nativeSendMessage(message)
+
+ fun adjustPadBufferSize(buffer: Int) = nativeAdjustPadBufferSize(buffer)
+
+ fun consumeBootSessionData(): Long {
+ return bootSessionDataPointer.also {
+ bootSessionDataPointer = 0
}
+ }
- releaseNetplayClient()
- false
+ suspend fun close() = withContext(Dispatchers.IO) {
+ closeBlocking()
}
- suspend fun quit() = withContext(Dispatchers.IO) {
- releaseNetplayClient()
+ @Synchronized
+ fun closeBlocking() {
+ if (isClosed) return
+ isClosed = true
+ sessionScope.cancel()
+ releaseNativeResources()
+ onClosed(this)
}
- @OptIn(ExperimentalCoroutinesApi::class)
- private fun releaseNetplayClient() {
- sessionScope?.cancel()
- sessionScope = null
+ protected fun finalize() {
+ releaseNativeResources()
+ }
- if (bootSessionDataPointer != 0L) {
- ReleaseBootSessionData()
+ private fun mergeMessages(): Flow<NetplayMessage> = merge(
+ chatMessages.map { NetplayMessage.Chat(it) },
+ game.map { NetplayMessage.GameChanged(it) },
+ hostInputAuthorityEnabled.map { NetplayMessage.HostInputAuthorityChanged(it) },
+ padBuffer.map { NetplayMessage.BufferChanged(it) },
+ )
+
+ private fun releaseNativeResources() {
+ val currentBootSessionDataPointer = bootSessionDataPointer
+ if (currentBootSessionDataPointer != 0L) {
bootSessionDataPointer = 0
+ nativeReleaseBootSessionData(currentBootSessionDataPointer)
}
- if (netPlayClientPointer != 0L) {
- ReleaseNetplayClient()
+ val currentNetPlayClientPointer = netPlayClientPointer
+ if (currentNetPlayClientPointer != 0L) {
netPlayClientPointer = 0
+ nativeReleaseClient(currentNetPlayClientPointer)
}
- _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
+ val currentNetPlayUICallbacksPointer = netPlayUICallbacksPointer
+ if (currentNetPlayUICallbacksPointer != 0L) {
+ netPlayUICallbacksPointer = 0
+ nativeReleaseUICallbacks(currentNetPlayUICallbacksPointer)
}
}
- @JvmStatic
- private external fun Join(): Long
+ // JNI methods
- @JvmStatic
- external fun isClientConnected(): Boolean
+ private external fun nativeCreateUICallbacks(): Long
- @JvmStatic
- external fun sendMessage(message: String)
+ private external fun nativeJoin(): Long
- @JvmStatic
- external fun adjustPadBufferSize(buffer: Int)
+ private external fun nativeSendMessage(message: String)
- @JvmStatic
- private external fun ReleaseBootSessionData()
+ private external fun nativeAdjustPadBufferSize(buffer: Int)
- @JvmStatic
- private external fun ReleaseNetplayClient()
+ private external fun nativeReleaseUICallbacks(pointer: Long)
- private fun mergeMessages(): Flow<NetplayMessage> = merge(
- chatMessages.map { NetplayMessage.Chat(it) },
- game.map { NetplayMessage.GameChanged(it) },
- hostInputAuthorityEnabled.map { NetplayMessage.HostInputAuthorityChanged(it) },
- padBuffer.map { NetplayMessage.BufferChanged(it) },
- )
+ 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() {
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplaySetupViewModel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplaySetupViewModel.kt
index 2a04e41c35..a9b34d3147 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplaySetupViewModel.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplaySetupViewModel.kt
@@ -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 }
-
- if (Netplay.join()) {
- _showNetplayScreen.trySend(Unit)
+ var errorForwarding: Job? = null
+
+ 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
}
+ }
+ }
+
+ 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()
+ }
- _connecting.value = false
+ 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
}
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt
index 66ad26aaec..96f9d7328b 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt
@@ -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
}
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt
index 8a8f860825..f2acf50107 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt
@@ -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)
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt
index 8058b3ad96..145cf7d635 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplaySetupActivity.kt
@@ -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)
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
index 49331d8a4c..c989cdc780 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
@@ -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) {
diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp
index 150cb8162e..2bee9e2138 100644
--- a/Source/Android/jni/AndroidCommon/IDCache.cpp
+++ b/Source/Android/jni/AndroidCommon/IDCache.cpp
@@ -29,8 +29,8 @@ static jclass s_game_file_cache_manager_class;
static jfieldID s_game_file_cache_manager_instance;
static jclass s_netplay_class;
+static jfieldID s_net_play_ui_callbacks_pointer;
static jfieldID s_net_play_client_pointer;
-static jfieldID s_netplay_boot_session_data_pointer;
static jmethodID s_netplay_on_boot_game;
static jmethodID s_netplay_on_stop_game;
static jmethodID s_netplay_on_connection_lost;
@@ -257,14 +257,14 @@ jclass GetNetplayClass()
return s_netplay_class;
}
-jfieldID GetNetPlayClientPointer()
+jfieldID GetNetPlayUICallbacksPointer()
{
- return s_net_play_client_pointer;
+ return s_net_play_ui_callbacks_pointer;
}
-jfieldID GetNetplayBootSessionDataPointer()
+jfieldID GetNetPlayClientPointer()
{
- return s_netplay_boot_session_data_pointer;
+ return s_net_play_client_pointer;
}
jmethodID GetNetplayOnBootGame()
@@ -742,29 +742,30 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
env->DeleteLocalRef(game_file_cache_manager_class);
const jclass netplay_class =
- env->FindClass("org/dolphinemu/dolphinemu/features/netplay/Netplay");
+ env->FindClass("org/dolphinemu/dolphinemu/features/netplay/NetplaySession");
s_netplay_class = reinterpret_cast<jclass>(env->NewGlobalRef(netplay_class));
- s_net_play_client_pointer = env->GetStaticFieldID(netplay_class, "netPlayClientPointer", "J");
- s_netplay_boot_session_data_pointer = env->GetStaticFieldID(netplay_class, "bootSessionDataPointer", "J");
- s_netplay_on_boot_game = env->GetStaticMethodID(netplay_class, "onBootGame", "(Ljava/lang/String;J)V");
- s_netplay_on_stop_game = env->GetStaticMethodID(netplay_class, "onStopGame", "()V");
- s_netplay_on_connection_lost = env->GetStaticMethodID(netplay_class, "onConnectionLost", "()V");
- s_netplay_on_connection_error = env->GetStaticMethodID(netplay_class, "onConnectionError", "(Ljava/lang/String;)V");
+ s_net_play_ui_callbacks_pointer =
+ env->GetFieldID(netplay_class, "netPlayUICallbacksPointer", "J");
+ s_net_play_client_pointer = env->GetFieldID(netplay_class, "netPlayClientPointer", "J");
+ s_netplay_on_boot_game = env->GetMethodID(netplay_class, "onBootGame", "(Ljava/lang/String;J)V");
+ s_netplay_on_stop_game = env->GetMethodID(netplay_class, "onStopGame", "()V");
+ s_netplay_on_connection_lost = env->GetMethodID(netplay_class, "onConnectionLost", "()V");
+ s_netplay_on_connection_error = env->GetMethodID(netplay_class, "onConnectionError", "(Ljava/lang/String;)V");
s_netplay_on_game_changed =
- env->GetStaticMethodID(netplay_class, "onGameChanged", "(Ljava/lang/String;)V");
+ env->GetMethodID(netplay_class, "onGameChanged", "(Ljava/lang/String;)V");
s_netplay_on_host_input_authority_changed =
- env->GetStaticMethodID(netplay_class, "onHostInputAuthorityChanged", "(Z)V");
+ env->GetMethodID(netplay_class, "onHostInputAuthorityChanged", "(Z)V");
s_netplay_on_pad_buffer_changed =
- env->GetStaticMethodID(netplay_class, "onPadBufferChanged", "(I)V");
+ env->GetMethodID(netplay_class, "onPadBufferChanged", "(I)V");
s_netplay_on_chat_message_received =
- env->GetStaticMethodID(netplay_class, "onChatMessageReceived", "(Ljava/lang/String;)V");
- s_netplay_update = env->GetStaticMethodID(netplay_class, "onUpdate", "([Lorg/dolphinemu/dolphinemu/features/netplay/model/Player;)V");
+ env->GetMethodID(netplay_class, "onChatMessageReceived", "(Ljava/lang/String;)V");
+ s_netplay_update = env->GetMethodID(netplay_class, "onUpdate", "([Lorg/dolphinemu/dolphinemu/features/netplay/model/Player;)V");
s_netplay_on_show_chunked_progress_dialog =
- env->GetStaticMethodID(netplay_class, "onShowChunkedProgressDialog", "(Ljava/lang/String;J[I)V");
+ env->GetMethodID(netplay_class, "onShowChunkedProgressDialog", "(Ljava/lang/String;J[I)V");
s_netplay_on_set_chunked_progress =
- env->GetStaticMethodID(netplay_class, "onSetChunkedProgress", "(IJ)V");
+ env->GetMethodID(netplay_class, "onSetChunkedProgress", "(IJ)V");
s_netplay_on_hide_chunked_progress_dialog =
- env->GetStaticMethodID(netplay_class, "onHideChunkedProgressDialog", "()V");
+ env->GetMethodID(netplay_class, "onHideChunkedProgressDialog", "()V");
env->DeleteLocalRef(netplay_class);
const jclass netplay_player_class =
diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h
index 4041b5060e..c5f0a45573 100644
--- a/Source/Android/jni/AndroidCommon/IDCache.h
+++ b/Source/Android/jni/AndroidCommon/IDCache.h
@@ -32,8 +32,8 @@ jclass GetGameFileCacheManagerClass();
jfieldID GetGameFileCacheManagerInstance();
jclass GetNetplayClass();
+jfieldID GetNetPlayUICallbacksPointer();
jfieldID GetNetPlayClientPointer();
-jfieldID GetNetplayBootSessionDataPointer();
jmethodID GetNetplayOnBootGame();
jmethodID GetNetplayOnStopGame();
jmethodID GetNetplayOnConnectionLost();
diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp
index 13471a6cd0..0d29ad3908 100644
--- a/Source/Android/jni/MainAndroid.cpp
+++ b/Source/Android/jni/MainAndroid.cpp
@@ -618,10 +618,10 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_Run___3Ljava_lang_String_2ZLjava_la
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RunNetPlay(
- JNIEnv* env, jclass, jobjectArray jPaths, jboolean jRiivolution)
+ JNIEnv* env, jclass, jobjectArray jPaths, jboolean jRiivolution, jlong jBootSessionData)
{
- auto boot_session_data = std::unique_ptr<BootSessionData>(reinterpret_cast<BootSessionData*>(
- env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetplayBootSessionDataPointer())));
+ auto boot_session_data = std::unique_ptr<BootSessionData>(
+ reinterpret_cast<BootSessionData*>(jBootSessionData));
if (!boot_session_data)
{
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetDisplayToastMsg(),
@@ -629,7 +629,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RunNetPlay(
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetFinishEmulationActivity());
return;
}
- env->SetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetplayBootSessionDataPointer(), 0);
Run(env, JStringArrayToVector(env, jPaths), jRiivolution, std::move(*boot_session_data));
}
diff --git a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
index fa013017dc..1f9e701edb 100644
--- a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
+++ b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
@@ -10,29 +10,54 @@
namespace NetPlay {
-NetPlayUICallbacks::NetPlayUICallbacks(std::vector<std::shared_ptr<const UICommon::GameFile>> games)
- : m_games(std::move(games))
+NetPlayUICallbacks::NetPlayUICallbacks(jobject netplay_session,
+ std::vector<std::shared_ptr<const UICommon::GameFile>> games)
+ : m_netplay_session(IDCache::GetEnvForThread()->NewWeakGlobalRef(netplay_session)),
+ m_games(std::move(games))
{
m_state_changed_hook = Core::AddOnStateChangedCallback([this](Core::State state) {
if ((state == Core::State::Uninitialized || state == Core::State::Stopping) &&
!m_got_stop_request)
{
JNIEnv* env = IDCache::GetEnvForThread();
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
auto* client = reinterpret_cast<NetPlay::NetPlayClient*>(
- env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetPlayClientPointer()));
+ env->GetLongField(netplay_session, IDCache::GetNetPlayClientPointer()));
if (client)
client->RequestStopGame();
+
+ env->DeleteLocalRef(netplay_session);
}
});
}
-NetPlayUICallbacks::~NetPlayUICallbacks() = default;
+NetPlayUICallbacks::~NetPlayUICallbacks()
+{
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->DeleteWeakGlobalRef(m_netplay_session);
+}
+
+jobject NetPlayUICallbacks::GetNetplaySessionLocalRef(JNIEnv* env) const
+{
+ return env->NewLocalRef(m_netplay_session);
+}
+
+void NetPlayUICallbacks::BootGame(const std::string& filename,
+ std::unique_ptr<BootSessionData> boot_session_data)
+{
+ m_got_stop_request = false;
+
+ JNIEnv* env = IDCache::GetEnvForThread();
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
-void NetPlayUICallbacks::BootGame(const std::string& filename, std::unique_ptr<BootSessionData> boot_session_data) {
- m_got_stop_request = false;
- JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnBootGame(),
- ToJString(env, filename), reinterpret_cast<jlong>(boot_session_data.release()));
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnBootGame(), ToJString(env, filename),
+ reinterpret_cast<jlong>(boot_session_data.release()));
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::StopGame()
@@ -41,8 +66,14 @@ void NetPlayUICallbacks::StopGame()
return;
m_got_stop_request = true;
+
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnStopGame());
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnStopGame());
+ env->DeleteLocalRef(netplay_session);
}
bool NetPlayUICallbacks::IsHosting() const { return false; }
@@ -50,10 +81,17 @@ bool NetPlayUICallbacks::IsHosting() const { return false; }
void NetPlayUICallbacks::Update()
{
JNIEnv* env = IDCache::GetEnvForThread();
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
auto* client = reinterpret_cast<NetPlay::NetPlayClient*>(
- env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetPlayClientPointer()));
+ env->GetLongField(netplay_session, IDCache::GetNetPlayClientPointer()));
if (!client)
+ {
+ env->DeleteLocalRef(netplay_session);
return;
+ }
const std::vector<const NetPlay::Player*> players = client->GetPlayers();
@@ -77,16 +115,21 @@ void NetPlayUICallbacks::Update()
env->DeleteLocalRef(player_obj);
}
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayUpdate(), player_array);
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayUpdate(), player_array);
env->DeleteLocalRef(player_array);
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::AppendChat(const std::string& message)
{
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
- IDCache::GetNetplayOnChatMessageReceived(),
- ToJString(env, message));
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnChatMessageReceived(),
+ ToJString(env, message));
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier,
@@ -96,8 +139,13 @@ void NetPlayUICallbacks::OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_ide
m_current_game_name = netplay_name;
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnGameChanged(),
- ToJString(env, netplay_name));
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnGameChanged(),
+ ToJString(env, netplay_name));
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::OnMsgChangeGBARom(int, const NetPlay::GBAConfig&) {}
@@ -105,13 +153,19 @@ void NetPlayUICallbacks::OnMsgChangeGBARom(int, const NetPlay::GBAConfig&) {}
void NetPlayUICallbacks::OnMsgStartGame()
{
JNIEnv* env = IDCache::GetEnvForThread();
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
auto* client = reinterpret_cast<NetPlay::NetPlayClient*>(
- env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetPlayClientPointer()));
+ env->GetLongField(netplay_session, IDCache::GetNetPlayClientPointer()));
if (client)
{
if (const auto game = FindGameFile(m_current_game_identifier))
client->StartGame(game->GetFilePath());
}
+
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::OnMsgStopGame() {}
@@ -122,16 +176,25 @@ void NetPlayUICallbacks::OnPlayerDisconnect(const std::string&) {}
void NetPlayUICallbacks::OnPadBufferChanged(u32 buffer)
{
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnPadBufferChanged(),
- static_cast<jint>(buffer));
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnPadBufferChanged(),
+ static_cast<jint>(buffer));
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::OnHostInputAuthorityChanged(bool enabled)
{
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
- IDCache::GetNetplayOnHostInputAuthorityChanged(),
- static_cast<jboolean>(enabled));
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnHostInputAuthorityChanged(),
+ static_cast<jboolean>(enabled));
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::OnDesync(u32, const std::string&) {}
@@ -139,14 +202,24 @@ void NetPlayUICallbacks::OnDesync(u32, const std::string&) {}
void NetPlayUICallbacks::OnConnectionLost()
{
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnConnectionLost());
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnConnectionLost());
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::OnConnectionError(const std::string& message)
{
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnConnectionError(),
- ToJString(env, message));
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnConnectionError(),
+ ToJString(env, message));
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::OnTraversalError(Common::TraversalClient::FailureReason) {}
@@ -191,29 +264,40 @@ void NetPlayUICallbacks::ShowChunkedProgressDialog(const std::string& title, u64
std::span<const int> players)
{
JNIEnv* env = IDCache::GetEnvForThread();
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
jintArray j_players = env->NewIntArray(static_cast<jsize>(players.size()));
env->SetIntArrayRegion(j_players, 0, static_cast<jsize>(players.size()), players.data());
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
- IDCache::GetNetplayOnShowChunkedProgressDialog(),
- ToJString(env, title), static_cast<jlong>(data_size), j_players);
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnShowChunkedProgressDialog(),
+ ToJString(env, title), static_cast<jlong>(data_size), j_players);
env->DeleteLocalRef(j_players);
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::HideChunkedProgressDialog()
{
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
- IDCache::GetNetplayOnHideChunkedProgressDialog());
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnHideChunkedProgressDialog());
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::SetChunkedProgress(int pid, u64 progress)
{
JNIEnv* env = IDCache::GetEnvForThread();
- env->CallStaticVoidMethod(IDCache::GetNetplayClass(),
- IDCache::GetNetplayOnSetChunkedProgress(),
- static_cast<jint>(pid), static_cast<jlong>(progress));
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnSetChunkedProgress(),
+ static_cast<jint>(pid), static_cast<jlong>(progress));
+ env->DeleteLocalRef(netplay_session);
}
void NetPlayUICallbacks::SetHostWiiSyncData(std::vector<u64>, std::string) {}
diff --git a/Source/Android/jni/NetPlay/NetPlayUICallbacks.h b/Source/Android/jni/NetPlay/NetPlayUICallbacks.h
index 10c1b1ac22..57eec55230 100644
--- a/Source/Android/jni/NetPlay/NetPlayUICallbacks.h
+++ b/Source/Android/jni/NetPlay/NetPlayUICallbacks.h
@@ -5,6 +5,8 @@
#include <string>
#include <vector>
+#include <jni.h>
+
#include "Common/HookableEvent.h"
#include "Core/NetPlayClient.h"
#include "UICommon/GameFile.h"
@@ -13,7 +15,8 @@ namespace NetPlay {
class NetPlayUICallbacks : public NetPlay::NetPlayUI {
public:
- NetPlayUICallbacks(std::vector<std::shared_ptr<const UICommon::GameFile>> games);
+ NetPlayUICallbacks(jobject netplay_session,
+ std::vector<std::shared_ptr<const UICommon::GameFile>> games);
~NetPlayUICallbacks() override;
void BootGame(const std::string& filename,
@@ -59,6 +62,9 @@ public:
void SetHostWiiSyncData(std::vector<u64> titles, std::string redirect_folder) override;
private:
+ jobject GetNetplaySessionLocalRef(JNIEnv* env) const;
+
+ jweak m_netplay_session;
std::vector<std::shared_ptr<const UICommon::GameFile>> m_games;
NetPlay::SyncIdentifier m_current_game_identifier;
std::string m_current_game_name;
diff --git a/Source/Android/jni/NetPlay/Netplay.cpp b/Source/Android/jni/NetPlay/Netplay.cpp
index 62905d203c..61c2e77b9f 100644
--- a/Source/Android/jni/NetPlay/Netplay.cpp
+++ b/Source/Android/jni/NetPlay/Netplay.cpp
@@ -18,51 +18,41 @@
#include "jni/AndroidCommon/IDCache.h"
#include "jni/NetPlay/NetPlayUICallbacks.h"
-static NetPlay::NetPlayClient* GetPointer(JNIEnv* env)
+static NetPlay::NetPlayUICallbacks* GetUICallbacksPointer(JNIEnv* env, jobject obj)
{
- return reinterpret_cast<NetPlay::NetPlayClient*>(
- env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetPlayClientPointer()));
+ return reinterpret_cast<NetPlay::NetPlayUICallbacks*>(
+ env->GetLongField(obj, IDCache::GetNetPlayUICallbacksPointer()));
}
-extern "C" {
-
-JNIEXPORT jboolean JNICALL
-Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_isClientConnected(JNIEnv* env, jclass)
+static NetPlay::NetPlayClient* GetClientPointer(JNIEnv* env, jobject obj)
{
- return static_cast<jboolean>(GetPointer(env)->IsConnected());
+ return reinterpret_cast<NetPlay::NetPlayClient*>(
+ env->GetLongField(obj, IDCache::GetNetPlayClientPointer()));
}
+extern "C" {
+
JNIEXPORT void JNICALL
-Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_sendMessage(JNIEnv* env, jclass,
- jstring jmessage)
+Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeSendMessage(JNIEnv* env, jobject obj,
+ jstring jmessage)
{
- if (auto* client = GetPointer(env))
+ if (auto* client = GetClientPointer(env, obj))
client->SendChatMessage(GetJString(env, jmessage));
}
JNIEXPORT void JNICALL
-Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_adjustPadBufferSize(JNIEnv* env, jclass,
- jint buffer)
+Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeAdjustPadBufferSize(JNIEnv* env,
+ jobject obj,
+ jint buffer)
{
- if (auto* client = GetPointer(env))
+ if (auto* client = GetClientPointer(env, obj))
client->AdjustPadBufferSize(static_cast<u32>(buffer));
}
JNIEXPORT jlong JNICALL
-Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_Join(JNIEnv* env, jclass)
+Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeCreateUICallbacks(JNIEnv* env,
+ jobject obj)
{
- const std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
- const bool is_traversal = traversal_choice == "traversal";
-
- std::string host_ip;
- host_ip = is_traversal ? Config::Get(Config::NETPLAY_HOST_CODE) :
- Config::Get(Config::NETPLAY_ADDRESS);
-
- const u16 host_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
- const std::string traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
- const u16 traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
- const std::string nickname = Config::Get(Config::NETPLAY_NICKNAME);
-
jobject jgame_file_cache = env->GetStaticObjectField(
IDCache::GetGameFileCacheManagerClass(), IDCache::GetGameFileCacheManagerInstance());
auto* game_file_cache = reinterpret_cast<UICommon::GameFileCache*>(
@@ -72,26 +62,58 @@ Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_Join(JNIEnv* env, jclass
game_file_cache->ForEach(
[&games](const std::shared_ptr<const UICommon::GameFile>& game) { games.push_back(game); });
+ return reinterpret_cast<jlong>(new NetPlay::NetPlayUICallbacks(obj, std::move(games)));
+}
+
+JNIEXPORT jlong JNICALL
+Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeJoin(JNIEnv* env, jobject obj)
+{
+ auto* ui = GetUICallbacksPointer(env, obj);
+
+ const std::string traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
+ const u16 traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
+ const std::string nickname = Config::Get(Config::NETPLAY_NICKNAME);
+
+ const std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
+ const bool is_traversal = traversal_choice == "traversal";
+ const std::string host_ip = is_traversal ? Config::Get(Config::NETPLAY_HOST_CODE) :
+ Config::Get(Config::NETPLAY_ADDRESS);
+ const u16 host_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
+
auto* client = new NetPlay::NetPlayClient(
- host_ip, host_port, new NetPlay::NetPlayUICallbacks(std::move(games)), nickname,
+ host_ip, host_port, ui, nickname,
NetPlay::NetTraversalConfig{is_traversal, traversal_host, traversal_port});
+ if (!client->IsConnected())
+ {
+ delete client;
+ return 0;
+ }
+
return reinterpret_cast<jlong>(client);
}
JNIEXPORT void JNICALL
-Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_ReleaseBootSessionData(JNIEnv* env, jclass)
+Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeReleaseUICallbacks(JNIEnv*,
+ jobject,
+ jlong pointer)
+{
+ delete reinterpret_cast<NetPlay::NetPlayUICallbacks*>(pointer);
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeReleaseBootSessionData(JNIEnv*,
+ jobject,
+ jlong pointer)
{
- auto* data = reinterpret_cast<BootSessionData*>(
- env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetplayBootSessionDataPointer()));
- delete data;
- env->SetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetplayBootSessionDataPointer(), 0);
+ delete reinterpret_cast<BootSessionData*>(pointer);
}
JNIEXPORT void JNICALL
-Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_ReleaseNetplayClient(JNIEnv* env, jclass)
+Java_org_dolphinemu_dolphinemu_features_netplay_NetplaySession_nativeReleaseClient(JNIEnv*, jobject,
+ jlong pointer)
{
- delete GetPointer(env);
+ delete reinterpret_cast<NetPlay::NetPlayClient*>(pointer);
}
} // extern "C"