Max buffer

This commit is contained in:
Tom Pratt
2026-04-15 22:23:02 +02:00
committed by Tom Pratt
parent e49451b28e
commit 65e4c60158
7 changed files with 199 additions and 2 deletions

View File

@@ -148,6 +148,9 @@ object Netplay {
@JvmStatic
external fun sendMessage(message: String)
@JvmStatic
external fun adjustPadBufferSize(buffer: Int)
@JvmStatic
private external fun ReleaseNetplayClient()
@@ -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,

View File

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

View File

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

View File

@@ -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 = {},
)
}

View File

@@ -1007,4 +1007,5 @@ It can efficiently compress both junk data and encrypted Wii data.
<string name="netplay_players_name">Name</string>
<string name="netplay_players_ping">Ping</string>
<string name="netplay_players_mapping">Mapping</string>
<string name="netplay_max_buffer">Max buffer</string>
</resources>

View File

@@ -99,7 +99,6 @@ void NetPlayUICallbacks::OnPlayerDisconnect(const std::string&) {}
void NetPlayUICallbacks::OnPadBufferChanged(u32 buffer)
{
//TODO handle host input authority = true
JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetNetplayClass(), IDCache::GetNetplayOnPadBufferChanged(),
static_cast<jint>(buffer));

View File

@@ -103,6 +103,19 @@ Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_getIndexPassword(JNIEnv*
return ToJString(env, Config::Get(Config::NETPLAY_INDEX_PASSWORD));
}
JNIEXPORT jint JNICALL
Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_getClientBufferSize(JNIEnv*, jclass)
{
return static_cast<jint>(Config::Get(Config::NETPLAY_CLIENT_BUFFER_SIZE));
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_setClientBufferSize(JNIEnv*, jclass,
jint buffer)
{
Config::SetBase(Config::NETPLAY_CLIENT_BUFFER_SIZE, static_cast<u32>(buffer));
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_SaveSetup(
JNIEnv* env, jclass, jstring jnickname, jstring traversalChoice, jstring jaddress,
@@ -144,6 +157,14 @@ Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_sendMessage(JNIEnv* env,
client->SendChatMessage(GetJString(env, jmessage));
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_adjustPadBufferSize(JNIEnv* env, jclass,
jint buffer)
{
if (auto* client = GetPointer(env))
client->AdjustPadBufferSize(static_cast<u32>(buffer));
}
JNIEXPORT jlong JNICALL
Java_org_dolphinemu_dolphinemu_features_netplay_Netplay_Join(JNIEnv* env, jclass)
{