Handle desync messages

Show them in the chat window and also in a toast during game play.
This commit is contained in:
Tom Pratt 2026-04-29 14:31:25 +02:00
parent 1285cb2282
commit 8792a4b924
7 changed files with 45 additions and 1 deletions

View File

@ -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()
@ -146,6 +152,7 @@ class NetplaySession(
game.map { NetplayMessage.GameChanged(it) },
hostInputAuthorityEnabled.map { NetplayMessage.HostInputAuthorityChanged(it) },
padBuffer.map { NetplayMessage.BufferChanged(it) },
desyncMessages,
)
private fun releaseNativeResources() {
@ -234,6 +241,11 @@ class NetplaySession(
_padBuffer.tryEmit(buffer)
}
@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()

View File

@ -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)
}
}

View File

@ -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,

View File

@ -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>

View File

@ -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 =

View File

@ -46,6 +46,7 @@ jmethodID GetNetplayUpdate();
jmethodID GetNetplayOnShowChunkedProgressDialog();
jmethodID GetNetplayOnSetChunkedProgress();
jmethodID GetNetplayOnHideChunkedProgressDialog();
jmethodID GetNetplayOnDesync();
jclass GetNetplayPlayerClass();
jmethodID GetNetplayPlayerConstructor();

View File

@ -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()
{