Add server & shared module and remove the serveur from the client app.
This commit is contained in:
parent
fa87f05be6
commit
3419afbe59
47 changed files with 233 additions and 530 deletions
|
|
@ -29,16 +29,15 @@ kotlin {
|
|||
api(libs.koin.core)
|
||||
implementation(libs.koin.compose)
|
||||
implementation(libs.koin.compose.viewmodel)
|
||||
// network.
|
||||
// common
|
||||
implementation(projects.shared)
|
||||
// network
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.cio)
|
||||
implementation(libs.ktor.client.websockets)
|
||||
implementation(libs.ktor.server.core)
|
||||
implementation(libs.ktor.server.netty)
|
||||
implementation(libs.ktor.server.websockets)
|
||||
// shell
|
||||
implementation(libs.lordcodes.turttle)
|
||||
implementation(libs.turtle)
|
||||
}
|
||||
|
||||
commonTest.dependencies {
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@
|
|||
<string name="network__host__label">host</string>
|
||||
<string name="network__port__label">port</string>
|
||||
<string name="network__socket__connect_action">Se connecter à la table</string>
|
||||
<string name="network__socket__host_action">Héberger la table</string>
|
||||
<string name="network__socket__disconnect_action">Se déconnecter</string>
|
||||
<string name="network__socket__status_state">État de connexion : %1$s</string>
|
||||
<string name="network__socket__status_connected">Connecté</string>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
package com.pixelized.desktop.lwa.repository.network
|
||||
|
||||
//import com.pixelized.desktop.lwa.repository.network.helper.server
|
||||
//import io.ktor.server.engine.EmbeddedServer
|
||||
//import io.ktor.server.netty.NettyApplicationEngine
|
||||
import com.pixelized.desktop.lwa.repository.network.helper.client
|
||||
import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
|
||||
import com.pixelized.desktop.lwa.repository.network.helper.server
|
||||
import com.pixelized.desktop.lwa.repository.network.protocol.Message
|
||||
import com.pixelized.desktop.lwa.repository.network.protocol.MessageContent
|
||||
import com.pixelized.desktop.lwa.utils.extention.decodeFromFrame
|
||||
import com.pixelized.desktop.lwa.utils.extention.encodeToFrame
|
||||
import com.pixelized.server.lwa.SERVER_PORT
|
||||
import com.pixelized.server.lwa.protocol.Message
|
||||
import com.pixelized.server.lwa.protocol.MessageContent
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.server.engine.EmbeddedServer
|
||||
import io.ktor.server.netty.NettyApplicationEngine
|
||||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readText
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -21,20 +23,17 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
|
||||
typealias Server = EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration>
|
||||
typealias Client = HttpClient
|
||||
|
||||
class NetworkRepository {
|
||||
companion object {
|
||||
const val DEFAULT_PORT = 16030
|
||||
const val DEFAULT_PORT = SERVER_PORT
|
||||
const val DEFAULT_HOST = "pixelized.freeboxos.fr"
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
private var networkJob: Job? = null
|
||||
private var server: Server? = null
|
||||
private var client: Client? = null
|
||||
|
||||
private val outgoingMessageBuffer = MutableSharedFlow<Message>()
|
||||
|
|
@ -47,65 +46,14 @@ class NetworkRepository {
|
|||
private val _status = MutableStateFlow(Status.DISCONNECTED)
|
||||
val status: StateFlow<Status> get() = _status
|
||||
|
||||
private val _type = MutableStateFlow(Type.NONE)
|
||||
val type: StateFlow<Type> get() = _type
|
||||
|
||||
fun onPlayerNameChange(player: String) {
|
||||
_player.value = player
|
||||
}
|
||||
|
||||
fun host(
|
||||
port: Int,
|
||||
) {
|
||||
_type.value = Type.SERVER
|
||||
_status.value = Status.CONNECTED
|
||||
|
||||
server = server(port = port) {
|
||||
println("Server launched")
|
||||
|
||||
val job = launch {
|
||||
// send local message to the clients
|
||||
outgoingMessageBuffer.collect { message ->
|
||||
send(Json.encodeToFrame(message = message))
|
||||
}
|
||||
}
|
||||
runCatching {
|
||||
// watching for clients incoming message
|
||||
incoming.consumeEach { frame ->
|
||||
if (frame is Frame.Text) {
|
||||
val message = Json.decodeFromFrame(frame = frame)
|
||||
incomingMessageBuffer.emit(message)
|
||||
// broadcast to clients the message
|
||||
outgoingMessageBuffer.emit(message)
|
||||
}
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
// TODO
|
||||
println("WebSocket exception: ${exception.localizedMessage}")
|
||||
}.also {
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
networkJob?.cancel()
|
||||
networkJob = scope.launch {
|
||||
try {
|
||||
server?.start(wait = true)
|
||||
} catch (exception: Exception) {
|
||||
// TODO
|
||||
println("WebSocket exception: ${exception.localizedMessage}")
|
||||
} finally {
|
||||
println("Server close")
|
||||
_type.value = Type.NONE
|
||||
_status.value = Status.DISCONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(
|
||||
host: String,
|
||||
port: Int,
|
||||
onConnect: (Type) -> Unit = { },
|
||||
onConnect: () -> Unit = { },
|
||||
onFailure: (Exception) -> Unit = { },
|
||||
onClose: () -> Unit = { },
|
||||
) {
|
||||
|
|
@ -115,9 +63,8 @@ class NetworkRepository {
|
|||
networkJob = scope.launch {
|
||||
try {
|
||||
client?.connectWebSocket(host = host, port = port) {
|
||||
_type.value = Type.CLIENT
|
||||
_status.value = Status.CONNECTED
|
||||
onConnect(Type.CLIENT)
|
||||
onConnect()
|
||||
|
||||
val job = launch {
|
||||
// send message to the server
|
||||
|
|
@ -140,7 +87,6 @@ class NetworkRepository {
|
|||
} catch (exception: Exception) {
|
||||
onFailure(exception)
|
||||
} finally {
|
||||
_type.value = Type.NONE
|
||||
_status.value = Status.DISCONNECTED
|
||||
onClose()
|
||||
}
|
||||
|
|
@ -150,7 +96,6 @@ class NetworkRepository {
|
|||
fun disconnect() {
|
||||
networkJob?.cancel()
|
||||
scope.launch {
|
||||
server?.stop()
|
||||
client?.close()
|
||||
}
|
||||
}
|
||||
|
|
@ -165,10 +110,6 @@ class NetworkRepository {
|
|||
)
|
||||
// emit the message into the outgoing buffer
|
||||
outgoingMessageBuffer.emit(message)
|
||||
// emit the message into the incoming buffer IF we are the server
|
||||
if (type.value == Type.SERVER) {
|
||||
incomingMessageBuffer.emit(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -176,20 +117,4 @@ class NetworkRepository {
|
|||
CONNECTED,
|
||||
DISCONNECTED
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
CLIENT,
|
||||
SERVER,
|
||||
NONE,
|
||||
}
|
||||
}
|
||||
|
||||
private fun Json.decodeFromFrame(frame: Frame.Text): Message {
|
||||
val json = frame.readText()
|
||||
return decodeFromString<Message>(json)
|
||||
}
|
||||
|
||||
private fun Json.encodeToFrame(message: Message): Frame {
|
||||
val json = encodeToJsonElement(message)
|
||||
return Frame.Text(text = json.toString())
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.repository.network.helper
|
||||
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.engine.EmbeddedServer
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import io.ktor.server.netty.NettyApplicationEngine
|
||||
import io.ktor.server.routing.routing
|
||||
import io.ktor.server.websocket.DefaultWebSocketServerSession
|
||||
import io.ktor.server.websocket.WebSockets
|
||||
import io.ktor.server.websocket.pingPeriod
|
||||
import io.ktor.server.websocket.timeout
|
||||
import io.ktor.server.websocket.webSocket
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
// https://ktor.io/docs/server-websockets.html#handle-multiple-session
|
||||
fun server(
|
||||
port: Int,
|
||||
handler: suspend DefaultWebSocketServerSession.() -> Unit
|
||||
): EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
|
||||
return embeddedServer(
|
||||
factory = Netty,
|
||||
port = port,
|
||||
module = {
|
||||
install(WebSockets) {
|
||||
pingPeriod = 15.seconds
|
||||
timeout = 15.seconds
|
||||
maxFrameSize = Long.MAX_VALUE
|
||||
masking = false
|
||||
}
|
||||
routing {
|
||||
webSocket(
|
||||
path = "/ws",
|
||||
handler = handler,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.repository.network.protocol
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Message(
|
||||
val from: String,
|
||||
val value: MessageContent,
|
||||
)
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.repository.network.protocol
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed interface MessageContent
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.pixelized.desktop.lwa.repository.network.protocol
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RollMessage(
|
||||
val skillLabel: String,
|
||||
val resultLabel: String?,
|
||||
val rollDifficulty: String?,
|
||||
val rollValue: Int,
|
||||
val rollSuccessLimit: Int?,
|
||||
) : MessageContent
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.pixelized.desktop.lwa.repository.roll_history
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.protocol.Message
|
||||
import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage
|
||||
import com.pixelized.server.lwa.protocol.Message
|
||||
import com.pixelized.server.lwa.protocol.RollMessage
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package com.pixelized.desktop.lwa.screen.network
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Type
|
||||
|
||||
class NetworkFactory {
|
||||
|
||||
fun convertToUio(
|
||||
player: String,
|
||||
status: Status,
|
||||
type: Type,
|
||||
host: String,
|
||||
port: Int,
|
||||
): NetworkPageUio {
|
||||
|
|
@ -18,7 +16,7 @@ class NetworkFactory {
|
|||
port = "$port",
|
||||
enableFields = status == Status.DISCONNECTED,
|
||||
enableActions = status == Status.DISCONNECTED && player.isNotBlank() && host.isNotBlank() && port > 0,
|
||||
enableCancel = type != Type.NONE && status == Status.CONNECTED
|
||||
enableCancel = status == Status.CONNECTED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,11 +48,9 @@ import lwacharactersheet.composeapp.generated.resources.network__player_name__la
|
|||
import lwacharactersheet.composeapp.generated.resources.network__port__label
|
||||
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
|
||||
import lwacharactersheet.composeapp.generated.resources.network__socket__disconnect_action
|
||||
import lwacharactersheet.composeapp.generated.resources.network__socket__host_action
|
||||
import lwacharactersheet.composeapp.generated.resources.network__title
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.annotation.KoinExperimentalAPI
|
||||
|
||||
@Stable
|
||||
data class NetworkPageUio(
|
||||
|
|
@ -64,7 +62,6 @@ data class NetworkPageUio(
|
|||
val enableCancel: Boolean,
|
||||
)
|
||||
|
||||
@OptIn(KoinExperimentalAPI::class)
|
||||
@Composable
|
||||
fun NetworkPage(
|
||||
viewModel: NetworkViewModel = koinViewModel(),
|
||||
|
|
@ -91,7 +88,6 @@ fun NetworkPage(
|
|||
onHostChange = viewModel::onHostChange,
|
||||
onPortChange = viewModel::onPortChange,
|
||||
onConnect = viewModel::connect,
|
||||
onHost = viewModel::host,
|
||||
onDisconnect = viewModel::disconnect,
|
||||
)
|
||||
}
|
||||
|
|
@ -133,7 +129,6 @@ private fun NetworkContent(
|
|||
onHostChange: (String) -> Unit,
|
||||
onPortChange: (String) -> Unit,
|
||||
onConnect: () -> Unit,
|
||||
onHost: () -> Unit,
|
||||
onDisconnect: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
|
|
@ -207,13 +202,6 @@ private fun NetworkContent(
|
|||
Text(text = stringResource(Res.string.network__socket__connect_action))
|
||||
}
|
||||
|
||||
TextButton(
|
||||
enabled = player.value.enableActions,
|
||||
onClick = onHost,
|
||||
) {
|
||||
Text(text = stringResource(Res.string.network__socket__host_action))
|
||||
}
|
||||
|
||||
TextButton(
|
||||
enabled = player.value.enableCancel,
|
||||
onClick = onDisconnect,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ class NetworkViewModel(
|
|||
private val repository: NetworkRepository,
|
||||
private val factory: NetworkFactory
|
||||
) : ViewModel() {
|
||||
|
||||
private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
|
||||
private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
|
||||
|
||||
|
|
@ -41,13 +40,11 @@ class NetworkViewModel(
|
|||
get() {
|
||||
val player = repository.player.collectAsState()
|
||||
val status = repository.status.collectAsState()
|
||||
val type = repository.type.collectAsState()
|
||||
return remember {
|
||||
derivedStateOf {
|
||||
factory.convertToUio(
|
||||
player = player.value,
|
||||
status = status.value,
|
||||
type = type.value,
|
||||
host = host.value,
|
||||
port = port.value,
|
||||
)
|
||||
|
|
@ -67,10 +64,6 @@ class NetworkViewModel(
|
|||
this.host.value = host
|
||||
}
|
||||
|
||||
fun host() {
|
||||
repository.host(port = port.value)
|
||||
}
|
||||
|
||||
fun connect() {
|
||||
controller.show()
|
||||
_isLoading.value = true
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.compose.runtime.State
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage
|
||||
import com.pixelized.server.lwa.protocol.RollMessage
|
||||
import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.pixelized.desktop.lwa.utils.extention
|
||||
|
||||
import com.pixelized.server.lwa.protocol.Message
|
||||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readText
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.encodeToJsonElement
|
||||
|
||||
fun Json.decodeFromFrame(frame: Frame.Text): Message {
|
||||
val json = frame.readText()
|
||||
return decodeFromString<Message>(json)
|
||||
}
|
||||
|
||||
fun Json.encodeToFrame(message: Message): Frame {
|
||||
val json = encodeToJsonElement(message)
|
||||
return Frame.Text(text = json.toString())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue