mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-08-16 15:27:11 -05:00
Merge pull request #14735 from tom-pratt/dual-core-warning
Android: Dual core warning when hosting 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