diff options
| author | Tom Pratt <tom.pratt@outlook.com> | 2026-04-15 22:23:02 +0200 |
|---|---|---|
| committer | Tom Pratt <tompratt@squareup.com> | 2026-05-19 12:02:56 +0200 |
| commit | 65e4c6015860c650d6afa844e86ca6f1ecd2c15a (patch) | |
| tree | e0ca2dbf2aed5d282f74c32a740da868efcada35 /Source/Android/app/src/main/java/org | |
| parent | e49451b28e9356e9378bf841ffdefa2ac3aa1b75 (diff) | |
Max buffer
Diffstat (limited to 'Source/Android/app/src/main/java/org')
4 files changed, 177 insertions, 1 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 88baa9f495..319929e71e 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 @@ -149,6 +149,9 @@ object Netplay { external fun sendMessage(message: String) @JvmStatic + external fun adjustPadBufferSize(buffer: Int) + + @JvmStatic private external fun ReleaseNetplayClient() private fun mergeMessages(): Flow<NetplayMessage> = merge( @@ -193,6 +196,8 @@ object Netplay { @JvmStatic fun onPadBufferChanged(buffer: Int) { + // Only for remote pad buffer settings. Ignore local max buffer changes. + if (_hostInputAuthorityEnabled.replayCache.firstOrNull() == true) return _padBuffer.tryEmit(buffer) } @@ -240,6 +245,12 @@ object Netplay { @JvmStatic external fun getIndexPassword(): String + @JvmStatic + external fun getClientBufferSize(): Int + + @JvmStatic + external fun setClientBufferSize(buffer: Int) + suspend fun saveSetup( nickname: String, connectionType: ConnectionType, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt index cc9dc00ae0..9da56d0c48 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/model/NetplayViewModel.kt @@ -10,7 +10,9 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel.Factory.CONFLATED +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -32,6 +34,12 @@ class NetplayViewModel : ViewModel() { val game = Netplay.game .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "") + val hostInputAuthority = Netplay.hostInputAuthorityEnabled + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false) + + private val _maxBuffer = MutableStateFlow(Netplay.getClientBufferSize()) + val maxBuffer = _maxBuffer.asStateFlow() + init { if (!Netplay.isClientConnected()) { _goBack.trySend(Unit) @@ -47,6 +55,12 @@ class NetplayViewModel : ViewModel() { Netplay.sendMessage(trimmedMessage) } + fun setMaxBuffer(buffer: Int) { + _maxBuffer.value = buffer + Netplay.setClientBufferSize(buffer) + Netplay.adjustPadBufferSize(buffer) + } + @OptIn(DelicateCoroutinesApi::class) override fun onCleared() { super.onCleared() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt index c4d0ded675..fec0823456 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayActivity.kt @@ -48,6 +48,9 @@ class NetplayActivity : AppCompatActivity(), ThemeProvider { onSendMessage = viewModel::sendMessage, game = viewModel.game.collectAsState().value, players = viewModel.players.collectAsState().value, + hostInputAuthorityEnabled = viewModel.hostInputAuthority.collectAsState().value, + maxBuffer = viewModel.maxBuffer.collectAsState().value, + onMaxBufferChanged = viewModel::setMaxBuffer, ) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt index 528c0d8a0b..73db806337 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/netplay/ui/NetplayScreen.kt @@ -8,25 +8,32 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Remove +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.ModalBottomSheet @@ -48,8 +55,12 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.dolphinemu.dolphinemu.R @@ -67,6 +78,9 @@ fun NetplayScreen( messages: List<NetplayMessage>, onSendMessage: (String) -> Unit, game: String, + hostInputAuthorityEnabled: Boolean, + maxBuffer: Int, + onMaxBufferChanged: (Int) -> Unit, players: List<Player>, ) { Scaffold( @@ -95,6 +109,9 @@ fun NetplayScreen( onSendMessage = onSendMessage, game = game, players = players, + hostInputAuthorityEnabled = hostInputAuthorityEnabled, + maxBuffer = maxBuffer, + onMaxBufferChanged = onMaxBufferChanged, modifier = modifier ) } else { @@ -103,6 +120,9 @@ fun NetplayScreen( onSendMessage = onSendMessage, game = game, players = players, + hostInputAuthorityEnabled = hostInputAuthorityEnabled, + maxBuffer = maxBuffer, + onMaxBufferChanged = onMaxBufferChanged, modifier = modifier ) } @@ -115,6 +135,9 @@ private fun PortraitContent( onSendMessage: (String) -> Unit, game: String, players: List<Player>, + hostInputAuthorityEnabled: Boolean, + maxBuffer: Int, + onMaxBufferChanged: (Int) -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -134,9 +157,12 @@ private fun PortraitContent( PlayersAndSettings( game = game, players = players, + hostInputAuthorityEnabled = hostInputAuthorityEnabled, + maxBuffer = maxBuffer, + onMaxBufferChanged = onMaxBufferChanged, modifier = Modifier .weight(1f) - .padding(horizontal = DolphinTheme.scaffoldPadding) + .padding(horizontal = DolphinTheme.scaffoldPadding), ) } } @@ -147,6 +173,9 @@ private fun LandscapeContent( onSendMessage: (String) -> Unit, game: String, players: List<Player>, + hostInputAuthorityEnabled: Boolean, + maxBuffer: Int, + onMaxBufferChanged: (Int) -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -164,6 +193,9 @@ private fun LandscapeContent( PlayersAndSettings( game = game, players = players, + hostInputAuthorityEnabled = hostInputAuthorityEnabled, + maxBuffer = maxBuffer, + onMaxBufferChanged = onMaxBufferChanged, modifier = Modifier .weight(1f) .padding(horizontal = DolphinTheme.scaffoldPadding) @@ -175,6 +207,9 @@ private fun LandscapeContent( private fun PlayersAndSettings( game: String, players: List<Player>, + hostInputAuthorityEnabled: Boolean, + maxBuffer: Int, + onMaxBufferChanged: (Int) -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -211,6 +246,16 @@ private fun PlayersAndSettings( .fillMaxWidth() ) } + + if (hostInputAuthorityEnabled) { + MenuSpacer() + + BufferInput( + value = maxBuffer, + onValueChange = onMaxBufferChanged, + label = stringResource(R.string.netplay_max_buffer), + ) + } } } @@ -353,6 +398,106 @@ private fun PlayersTable( } } +@Composable +private fun BufferInput( + value: Int, + onValueChange: (Int) -> Unit, + label: String, +) { + val range = 0..99 + var maybeEmptyValue by remember(value) { + mutableStateOf("$value") + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + ) { + OutlinedTextField( + value = TextFieldValue( + text = maybeEmptyValue, + selection = TextRange(maybeEmptyValue.length) + ), + onValueChange = { newValue -> + if (newValue.text.isEmpty()) { + maybeEmptyValue = newValue.text + return@OutlinedTextField + } + newValue.text.toIntOrNull()?.let { + if (it in range) { + onValueChange(it) + } + } + }, + label = { Text(label) }, + textStyle = LocalTextStyle.current.copy( + textAlign = TextAlign.Center + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + ), + singleLine = true, + modifier = Modifier + .weight(1f) + ) + + Spacer(modifier = Modifier.width(12.dp)) + + Button( + onClick = { + if (maybeEmptyValue.isEmpty()) { + maybeEmptyValue = "0" + onValueChange(0) + } else { + val newValue = value - 1 + if (newValue in range) { + onValueChange(newValue) + } + } + }, + shape = RoundedCornerShape( + topStartPercent = 50, + topEndPercent = 0, + bottomEndPercent = 0, + bottomStartPercent = 50, + ), + modifier = Modifier + .height(60.dp) + .padding(top = 8.dp) + ) { + Icon(Icons.Filled.Remove, contentDescription = "Back") + } + + Spacer(modifier = Modifier.width(2.dp)) + + Button( + onClick = { + if (maybeEmptyValue.isEmpty()) { + maybeEmptyValue = "0" + onValueChange(0) + } else { + val newValue = value + 1 + if (newValue in range) { + onValueChange(newValue) + } + } + }, + shape = RoundedCornerShape( + topStartPercent = 0, + topEndPercent = 50, + bottomEndPercent = 50, + bottomStartPercent = 0, + ), + modifier = Modifier + .height(60.dp) + .padding(top = 8.dp) + ) { + Icon(Icons.Filled.Add, contentDescription = "Back") + } + } +} + @Preview @Composable private fun NetplayScreenPreview() { @@ -419,5 +564,8 @@ private fun PreviewNetplayScreen() { }, onSendMessage = {}, game = "Game name", + hostInputAuthorityEnabled = true, + maxBuffer = 10, + onMaxBufferChanged = {}, ) } |
