summaryrefslogtreecommitdiff
path: root/Source/Android
diff options
context:
space:
mode:
authorTom Pratt <tom.pratt@outlook.com>2026-04-22 18:46:12 +0200
committerTom Pratt <tompratt@squareup.com>2026-05-19 12:02:56 +0200
commit38b5c7370c47a6760bfce4cb8f9c45ab5d403dbb (patch)
tree7134ed6dea86f946d184f39633ba1fcc0174af5c /Source/Android
parent86956b21bea7f6450f180fddf2ffcb2e1077a3b6 (diff)
Implement StopGame callback and use it to finish the emulation activity
Diffstat (limited to 'Source/Android')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/Netplay.kt13
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt7
-rw-r--r--Source/Android/jni/AndroidCommon/IDCache.cpp7
-rw-r--r--Source/Android/jni/AndroidCommon/IDCache.h1
-rw-r--r--Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp24
-rw-r--r--Source/Android/jni/NetPlay/NetPlayUICallbacks.h3
6 files changed, 53 insertions, 2 deletions
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/Netplay.kt
index 94de9bd98c..a90d1b00d8 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/Netplay.kt
@@ -44,6 +44,9 @@ object Netplay {
private val _launchGame = Channel<String>(Channel.CONFLATED)
val launchGame = _launchGame.receiveAsFlow()
+ private val _stopGame = Channel<Unit>(Channel.CONFLATED)
+ val stopGame = _stopGame.receiveAsFlow()
+
private val _connectionErrors = Channel<String>(Channel.BUFFERED)
val connectionErrors = _connectionErrors.receiveAsFlow()
@@ -123,6 +126,7 @@ object Netplay {
}
_launchGame.flush()
+ _stopGame.flush()
_connectionErrors.flush()
_players.resetReplayCache()
_messages.resetReplayCache()
@@ -166,9 +170,16 @@ object Netplay {
@JvmStatic
fun onBootGame(gameFilePath: String, bootSessionDataPointer: Long) {
this.bootSessionDataPointer = bootSessionDataPointer
+ _stopGame.flush()
_launchGame.trySend(gameFilePath)
}
+ @Keep
+ @JvmStatic
+ fun onStopGame() {
+ _stopGame.trySend(Unit)
+ }
+
@JvmStatic
fun onConnectionError(message: String) {
_connectionErrors.trySend(message)
@@ -244,6 +255,6 @@ object Netplay {
}
}
-private fun Channel<String>.flush() {
+private fun <T> Channel<T>.flush() {
while (this.tryReceive().isSuccess) Unit
}
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 4ee4aee8aa..61a5493950 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
@@ -10,6 +10,9 @@ import android.view.SurfaceHolder
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.activities.EmulationActivity
import org.dolphinemu.dolphinemu.databinding.FragmentEmulationBinding
@@ -220,6 +223,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val paths = requireNotNull(gamePaths) {
"Cannot start emulation without any game paths"
}
+ lifecycleScope.launch {
+ Netplay.stopGame.first()
+ stopEmulation()
+ }
NativeLibrary.RunNetPlay(paths, riivolution)
} else {
Log.debug("[EmulationFragment] Starting emulation thread.")
diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp
index 0ea1b77c54..bc59fe0053 100644
--- a/Source/Android/jni/AndroidCommon/IDCache.cpp
+++ b/Source/Android/jni/AndroidCommon/IDCache.cpp
@@ -32,6 +32,7 @@ static jclass s_netplay_class;
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_error;
static jmethodID s_netplay_on_game_changed;
static jmethodID s_netplay_on_host_input_authority_changed;
@@ -267,6 +268,11 @@ jmethodID GetNetplayOnBootGame()
return s_netplay_on_boot_game;
}
+jmethodID GetNetplayOnStopGame()
+{
+ return s_netplay_on_stop_game;
+}
+
jmethodID GetNetplayOnConnectionError()
{
return s_netplay_on_connection_error;
@@ -717,6 +723,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
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_error = env->GetStaticMethodID(netplay_class, "onConnectionError", "(Ljava/lang/String;)V");
s_netplay_on_game_changed =
env->GetStaticMethodID(netplay_class, "onGameChanged", "(Ljava/lang/String;)V");
diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h
index f61c0265d9..5646669f18 100644
--- a/Source/Android/jni/AndroidCommon/IDCache.h
+++ b/Source/Android/jni/AndroidCommon/IDCache.h
@@ -35,6 +35,7 @@ jclass GetNetplayClass();
jfieldID GetNetPlayClientPointer();
jfieldID GetNetplayBootSessionDataPointer();
jmethodID GetNetplayOnBootGame();
+jmethodID GetNetplayOnStopGame();
jmethodID GetNetplayOnConnectionError();
jmethodID GetNetplayOnGameChanged();
jmethodID GetNetplayOnHostInputAuthorityChanged();
diff --git a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
index 8f80d336b0..dd25d54280 100644
--- a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
+++ b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
@@ -4,6 +4,7 @@
#include "UICommon/GameFile.h"
#include "NetPlayUICallbacks.h"
#include "Core/Boot/Boot.h"
+#include "Core/Core.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
@@ -12,17 +13,38 @@ namespace NetPlay {
NetPlayUICallbacks::NetPlayUICallbacks(std::vector<std::shared_ptr<const UICommon::GameFile>> games)
: 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();
+ auto* client = reinterpret_cast<NetPlay::NetPlayClient*>(
+ env->GetStaticLongField(IDCache::GetNetplayClass(), IDCache::GetNetPlayClientPointer()));
+ if (client)
+ client->RequestStopGame();
+ }
+ });
}
NetPlayUICallbacks::~NetPlayUICallbacks() = default;
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()));
}
-void NetPlayUICallbacks::StopGame() {}
+void NetPlayUICallbacks::StopGame()
+{
+ if (m_got_stop_request)
+ return;
+
+ m_got_stop_request = true;
+ JNIEnv* env = IDCache::GetEnvForThread();
+ env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnStopGame());
+}
+
bool NetPlayUICallbacks::IsHosting() const { return false; }
void NetPlayUICallbacks::Update()
diff --git a/Source/Android/jni/NetPlay/NetPlayUICallbacks.h b/Source/Android/jni/NetPlay/NetPlayUICallbacks.h
index acccfccabf..10c1b1ac22 100644
--- a/Source/Android/jni/NetPlay/NetPlayUICallbacks.h
+++ b/Source/Android/jni/NetPlay/NetPlayUICallbacks.h
@@ -5,6 +5,7 @@
#include <string>
#include <vector>
+#include "Common/HookableEvent.h"
#include "Core/NetPlayClient.h"
#include "UICommon/GameFile.h"
@@ -61,6 +62,8 @@ private:
std::vector<std::shared_ptr<const UICommon::GameFile>> m_games;
NetPlay::SyncIdentifier m_current_game_identifier;
std::string m_current_game_name;
+ Common::EventHook m_state_changed_hook;
+ bool m_got_stop_request = true;
};
} // namespace NetPlay