summaryrefslogtreecommitdiff
path: root/Source/Android
diff options
context:
space:
mode:
authorTom Pratt <tom.pratt@outlook.com>2026-04-29 14:31:25 +0200
committerTom Pratt <tompratt@squareup.com>2026-05-19 12:02:58 +0200
commit117d1d71acc6b85b6c67396bd2222fbee7f0ff05 (patch)
treeca21c19d02b49d6dd1fe9c62683a0689b92019e6 /Source/Android
parentabd324e98d735d836d8131cef91b58f3278bc6fa (diff)
Handle desync messages
Show them in the chat window and also in a toast during game play.
Diffstat (limited to 'Source/Android')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt12
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayMessage.kt5
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt7
-rw-r--r--Source/Android/app/src/main/res/values/strings.xml1
-rw-r--r--Source/Android/jni/AndroidCommon/IDCache.cpp8
-rw-r--r--Source/Android/jni/AndroidCommon/IDCache.h1
-rw-r--r--Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp12
7 files changed, 45 insertions, 1 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
index f47d9ab198..fbeba1510c 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/NetplaySession.kt
@@ -95,6 +95,12 @@ class NetplaySession(
)
val padBuffer = _padBuffer.asSharedFlow()
+ private val _desyncMessages = MutableSharedFlow<NetplayMessage.Desync>(
+ extraBufferCapacity = 32,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ val desyncMessages = _desyncMessages.asSharedFlow()
+
private val _saveTransferProgress = MutableStateFlow<SaveTransferProgress?>(null)
val saveTransferProgress = _saveTransferProgress.asStateFlow()
@@ -148,6 +154,7 @@ class NetplaySession(
game.map { NetplayMessage.GameChanged(it) },
hostInputAuthorityEnabled.map { NetplayMessage.HostInputAuthorityChanged(it) },
padBuffer.map { NetplayMessage.BufferChanged(it) },
+ desyncMessages,
)
private fun releaseNativeResources() {
@@ -237,6 +244,11 @@ class NetplaySession(
}
@Keep
+ fun onDesync(frame: Int, player: String) {
+ _desyncMessages.tryEmit(NetplayMessage.Desync(player, frame))
+ }
+
+ @Keep
fun onShowChunkedProgressDialog(title: String, dataSize: Long, playerIds: IntArray) {
val players = _players.replayCache.firstOrNull()
_saveTransferProgress.value = SaveTransferProgress(
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayMessage.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayMessage.kt
index 41ab8997bd..b8291c094b 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayMessage.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayMessage.kt
@@ -26,4 +26,9 @@ sealed class NetplayMessage {
override fun message(context: Context) =
context.getString(R.string.netplay_message_buffer_changed, buffer)
}
+
+ class Desync(private val player: String, private val frame: Int) : NetplayMessage() {
+ override fun message(context: Context) =
+ context.getString(R.string.netplay_message_desync, player, frame)
+ }
}
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 c989cdc780..a005995bdb 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
@@ -9,9 +9,12 @@ import android.view.LayoutInflater
import android.view.SurfaceHolder
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.activities.EmulationActivity
@@ -231,6 +234,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
netplaySession.stopGame.first()
stopEmulation()
}
+ netplaySession
+ .desyncMessages
+ .onEach { Toast.makeText(requireContext(), it.message(requireContext()), Toast.LENGTH_SHORT).show() }
+ .launchIn(lifecycleScope)
NativeLibrary.RunNetPlay(
paths,
riivolution,
diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml
index cc1866d473..4e9c977faa 100644
--- a/Source/Android/app/src/main/res/values/strings.xml
+++ b/Source/Android/app/src/main/res/values/strings.xml
@@ -1002,6 +1002,7 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="netplay_message_game_changed">Game changed to %1$s</string>
<string name="netplay_message_buffer_changed">Buffer size changed to %1$d</string>
<string name="netplay_message_host_input_authority_changed">"Host input authority %1$s"</string>
+ <string name="netplay_message_desync">Possible desync detected: %1$s might have desynced at frame %2$d</string>
<string name="netplay_game_label">Game</string>
<string name="netplay_players_label">Players</string>
<string name="netplay_players_name">Name</string>
diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp
index 2bee9e2138..caf305fc9b 100644
--- a/Source/Android/jni/AndroidCommon/IDCache.cpp
+++ b/Source/Android/jni/AndroidCommon/IDCache.cpp
@@ -43,6 +43,7 @@ static jmethodID s_netplay_update;
static jmethodID s_netplay_on_show_chunked_progress_dialog;
static jmethodID s_netplay_on_set_chunked_progress;
static jmethodID s_netplay_on_hide_chunked_progress_dialog;
+static jmethodID s_netplay_on_desync;
static jclass s_netplay_player_class;
static jmethodID s_netplay_player_constructor;
@@ -327,6 +328,11 @@ jmethodID GetNetplayOnHideChunkedProgressDialog()
return s_netplay_on_hide_chunked_progress_dialog;
}
+jmethodID GetNetplayOnDesync()
+{
+ return s_netplay_on_desync;
+}
+
jclass GetNetplayPlayerClass()
{
return s_netplay_player_class;
@@ -766,6 +772,8 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
env->GetMethodID(netplay_class, "onSetChunkedProgress", "(IJ)V");
s_netplay_on_hide_chunked_progress_dialog =
env->GetMethodID(netplay_class, "onHideChunkedProgressDialog", "()V");
+ s_netplay_on_desync =
+ env->GetMethodID(netplay_class, "onDesync", "(ILjava/lang/String;)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 c5f0a45573..ae86740f09 100644
--- a/Source/Android/jni/AndroidCommon/IDCache.h
+++ b/Source/Android/jni/AndroidCommon/IDCache.h
@@ -46,6 +46,7 @@ jmethodID GetNetplayUpdate();
jmethodID GetNetplayOnShowChunkedProgressDialog();
jmethodID GetNetplayOnSetChunkedProgress();
jmethodID GetNetplayOnHideChunkedProgressDialog();
+jmethodID GetNetplayOnDesync();
jclass GetNetplayPlayerClass();
jmethodID GetNetplayPlayerConstructor();
diff --git a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
index 1f9e701edb..c411119ab9 100644
--- a/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
+++ b/Source/Android/jni/NetPlay/NetPlayUICallbacks.cpp
@@ -197,7 +197,17 @@ void NetPlayUICallbacks::OnHostInputAuthorityChanged(bool enabled)
env->DeleteLocalRef(netplay_session);
}
-void NetPlayUICallbacks::OnDesync(u32, const std::string&) {}
+void NetPlayUICallbacks::OnDesync(u32 frame, const std::string& player)
+{
+ JNIEnv* env = IDCache::GetEnvForThread();
+ jobject netplay_session = GetNetplaySessionLocalRef(env);
+ if (!netplay_session)
+ return;
+
+ env->CallVoidMethod(netplay_session, IDCache::GetNetplayOnDesync(),
+ static_cast<jint>(frame), ToJString(env, player));
+ env->DeleteLocalRef(netplay_session);
+}
void NetPlayUICallbacks::OnConnectionLost()
{