mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-08-19 17:13:29 -05:00
Android: Dual core warning when hosting netplay
Using dual core during netplay seems sufficiently detrimental that its worth showing this warning message and the option to turn dual core off when starting netplay.
This commit is contained in:
@@ -11,15 +11,16 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.dolphinemu.dolphinemu.features.netplay.NetplaySession
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
|
||||
@@ -104,18 +105,37 @@ class NetplayViewModel(
|
||||
private val _notAllPlayersHaveGame = Channel<Unit>(Channel.CONFLATED)
|
||||
val notAllPlayersHaveGame = _notAllPlayersHaveGame.receiveAsFlow()
|
||||
|
||||
private val _dualCoreWarning = Channel<Unit>(Channel.CONFLATED)
|
||||
val dualCoreWarning = _dualCoreWarning.receiveAsFlow()
|
||||
|
||||
fun startGame() {
|
||||
if (netplaySession.doAllPlayersHaveGame()) {
|
||||
netplaySession.startGame()
|
||||
} else {
|
||||
if (!netplaySession.doAllPlayersHaveGame()) {
|
||||
_notAllPlayersHaveGame.trySend(Unit)
|
||||
return
|
||||
}
|
||||
|
||||
if (BooleanSetting.MAIN_CPU_THREAD.boolean &&
|
||||
!BooleanSetting.NETPLAY_SKIP_DUAL_CORE_WARNING.boolean
|
||||
) {
|
||||
_dualCoreWarning.trySend(Unit)
|
||||
return
|
||||
}
|
||||
|
||||
confirmStartGame()
|
||||
}
|
||||
|
||||
fun confirmStartGame() {
|
||||
netplaySession.startGame()
|
||||
}
|
||||
|
||||
fun setDualCoreEnabled(enabled: Boolean) {
|
||||
BooleanSetting.MAIN_CPU_THREAD.setBoolean(NativeConfig.LAYER_BASE, enabled)
|
||||
}
|
||||
|
||||
fun skipDualCoreWarning() {
|
||||
BooleanSetting.NETPLAY_SKIP_DUAL_CORE_WARNING.setBoolean(NativeConfig.LAYER_BASE, true)
|
||||
}
|
||||
|
||||
fun sendMessage(message: String) {
|
||||
val trimmedMessage = message.trim()
|
||||
if (trimmedMessage.isEmpty()) {
|
||||
@@ -176,12 +196,14 @@ class NetplayViewModel(
|
||||
JoinInfoType.EXTERNAL to JoinAddress.Loading,
|
||||
)
|
||||
}
|
||||
|
||||
is TraversalState.Connected -> {
|
||||
_joinAddresses.value += mapOf(
|
||||
JoinInfoType.ROOM_ID to JoinAddress.Loaded(state.hostCode),
|
||||
JoinInfoType.EXTERNAL to JoinAddress.Loaded(state.externalAddress),
|
||||
)
|
||||
}
|
||||
|
||||
is TraversalState.Failure -> {
|
||||
_joinAddresses.value += mapOf(
|
||||
JoinInfoType.ROOM_ID to JoinAddress.Unknown(retry),
|
||||
|
||||
@@ -58,6 +58,9 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider {
|
||||
onGameSelected = viewModel::changeGame,
|
||||
gameFiles = viewModel.gameFiles.collectAsState().value,
|
||||
notAllPlayersHaveGame = viewModel.notAllPlayersHaveGame,
|
||||
dualCoreWarning = viewModel.dualCoreWarning,
|
||||
onSetDualCoreEnabled = viewModel::setDualCoreEnabled,
|
||||
onSkipDualCoreWarning = viewModel::skipDualCoreWarning,
|
||||
onConfirmStartGame = viewModel::confirmStartGame,
|
||||
players = viewModel.players.collectAsState().value,
|
||||
hostInputAuthorityEnabled = viewModel.hostInputAuthority.collectAsState().value,
|
||||
|
||||
@@ -5,6 +5,7 @@ package org.dolphinemu.dolphinemu.features.netplay.ui
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -37,6 +38,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Remove
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
@@ -59,6 +61,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
@@ -72,7 +75,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -87,7 +93,6 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import coil.compose.AsyncImage
|
||||
@@ -129,6 +134,9 @@ fun NetplayScreen(
|
||||
onGameSelected: (GameFile) -> Unit,
|
||||
gameFiles: List<GameFile>,
|
||||
notAllPlayersHaveGame: Flow<Unit>,
|
||||
dualCoreWarning: Flow<Unit>,
|
||||
onSetDualCoreEnabled: (Boolean) -> Unit,
|
||||
onSkipDualCoreWarning: () -> Unit,
|
||||
onConfirmStartGame: () -> Unit,
|
||||
hostInputAuthorityEnabled: Boolean,
|
||||
networkMode: NetworkMode,
|
||||
@@ -252,6 +260,11 @@ fun NetplayScreen(
|
||||
notAllPlayersHaveGame.collect { showNotAllPlayersHaveGame = true }
|
||||
}
|
||||
|
||||
var showDualCoreWarning by rememberSaveable { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
dualCoreWarning.collect { showDualCoreWarning = true }
|
||||
}
|
||||
|
||||
var dismissSaveTransferProgressDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (saveTransferProgress == null) {
|
||||
dismissSaveTransferProgressDialog = false
|
||||
@@ -323,6 +336,21 @@ fun NetplayScreen(
|
||||
onDismissRequest = { showNotAllPlayersHaveGame = false },
|
||||
)
|
||||
}
|
||||
|
||||
showDualCoreWarning -> {
|
||||
DualCoreWarningDialog(
|
||||
onSetDualCoreEnabled = onSetDualCoreEnabled,
|
||||
onStartGame = {
|
||||
showDualCoreWarning = false
|
||||
onConfirmStartGame()
|
||||
},
|
||||
onSkipWarning = {
|
||||
showDualCoreWarning = false
|
||||
onSkipDualCoreWarning()
|
||||
},
|
||||
onDismiss = { showDualCoreWarning = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1273,6 +1301,73 @@ private fun GameDigestPlayerRow(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DualCoreWarningDialog(
|
||||
onSetDualCoreEnabled: (Boolean) -> Unit,
|
||||
onStartGame: () -> Unit,
|
||||
onSkipWarning: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
var dualCoreEnabled by rememberSaveable { mutableStateOf(true) }
|
||||
AlertDialog(
|
||||
title = { Text(stringResource(R.string.netplay_dual_core_warning_title)) },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.netplay_dual_core_warning_message),
|
||||
)
|
||||
val ripplePadding = 12.dp
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.layout { measurable, constraints ->
|
||||
// Enlarge the ripple area beyond the dialog's padding
|
||||
val hPx = ripplePadding.roundToPx()
|
||||
val placeable = measurable.measure(
|
||||
constraints.copy(
|
||||
minWidth = constraints.maxWidth + hPx * 2,
|
||||
maxWidth = constraints.maxWidth + hPx * 2
|
||||
)
|
||||
)
|
||||
layout(constraints.maxWidth, placeable.height) {
|
||||
placeable.place(-hPx, 0)
|
||||
}
|
||||
}
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.clickable {
|
||||
dualCoreEnabled = !dualCoreEnabled
|
||||
onSetDualCoreEnabled(dualCoreEnabled)
|
||||
}
|
||||
.padding(ripplePadding),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.dual_core),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Switch(
|
||||
checked = dualCoreEnabled,
|
||||
onCheckedChange = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onStartGame) {
|
||||
Text(stringResource(R.string.netplay_start))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onSkipWarning) {
|
||||
Text(stringResource(R.string.netplay_dont_warn_again))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NetplayMessage.color(): Color {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
@@ -1361,6 +1456,9 @@ private fun PreviewNetplayScreen() {
|
||||
onGameSelected = {},
|
||||
gameFiles = emptyList(),
|
||||
notAllPlayersHaveGame = emptyFlow(),
|
||||
dualCoreWarning = emptyFlow(),
|
||||
onSetDualCoreEnabled = {},
|
||||
onSkipDualCoreWarning = {},
|
||||
onConfirmStartGame = {},
|
||||
hostInputAuthorityEnabled = true,
|
||||
networkMode = NetworkMode.HOST_INPUT_AUTHORITY,
|
||||
|
||||
@@ -947,7 +947,8 @@ enum class BooleanSetting(
|
||||
"ProgressEnabled",
|
||||
false
|
||||
),
|
||||
NETPLAY_USE_UPNP(Settings.FILE_DOLPHIN, Settings.SECTION_INI_NETPLAY, "UseUPNP", false);
|
||||
NETPLAY_USE_UPNP(Settings.FILE_DOLPHIN, Settings.SECTION_INI_NETPLAY, "UseUPNP", false),
|
||||
NETPLAY_SKIP_DUAL_CORE_WARNING(Settings.FILE_DOLPHIN, Settings.SECTION_INI_NETPLAY, "SkipDualCoreWarning", false);
|
||||
|
||||
override val isOverridden: Boolean
|
||||
get() = NativeConfig.isOverridden(file, section, key)
|
||||
|
||||
@@ -1012,6 +1012,9 @@ It can efficiently compress both junk data and encrypted Wii data.
|
||||
<string name="netplay_start">Start</string>
|
||||
<string name="netplay_start_warning_title">Warning</string>
|
||||
<string name="netplay_start_warning_not_all_players_have_game">Not all players have the game. Do you really want to start?</string>
|
||||
<string name="netplay_dual_core_warning_title">Dual Core Enabled</string>
|
||||
<string name="netplay_dual_core_warning_message">Dual core during netplay is not recommended. It can cause performance issues and prevent certain games from booting.</string>
|
||||
<string name="netplay_dont_warn_again">Don\'t warn again</string>
|
||||
<string name="netplay_chat_label">Chat</string>
|
||||
<string name="netplay_chat_send">Send</string>
|
||||
<string name="netplay_message_game_changed">Game changed to %1$s</string>
|
||||
|
||||
Reference in New Issue
Block a user