Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
Bruce0203 committed Aug 3, 2023
1 parent a46ed7f commit 93bc53c
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 65 deletions.
6 changes: 4 additions & 2 deletions client/src/commonMain/kotlin/start.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import korlibs.image.bitmap.NativeImage
import korlibs.image.font.Font
import korlibs.image.font.readWoffFont
import korlibs.image.format.*
Expand All @@ -7,12 +6,14 @@ import korlibs.korge.Korge
import korlibs.korge.scene.SceneContainer
import korlibs.korge.scene.sceneContainer
import korlibs.math.geom.ScaleMode
import network.client
import org.koin.core.qualifier.named
import org.koin.dsl.bind
import org.koin.dsl.module
import org.koin.mp.KoinPlatform.getKoin
import scene.MainScene
import util.ColorPalette
import websocket.startWebSocket
import kotlin.coroutines.coroutineContext

lateinit var sceneContainer: SceneContainer
Expand All @@ -27,7 +28,8 @@ suspend fun start() {
single(named("logo")) { logo }

}))

client()
startWebSocket()
Korge(title = "Skeep", icon = "images/logo.png", scaleMode = ScaleMode.COVER, backgroundColor = ColorPalette.background) {
sceneContainer = sceneContainer()
sceneContainer.changeTo({ MainScene() })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ class TextEditController(

private fun setTextNoSnapshot(text: String, out: TextSnapshot = TextSnapshot("", 0..0)): TextSnapshot? {
if (!acceptTextChange(textView.text, text)) return null
println("'$text'")
out.text = textView.text
out.selectionRange = selectionRange
textView.text = text
Expand Down
48 changes: 32 additions & 16 deletions client/src/commonMain/kotlin/ui/waitingRoom.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package ui

import io.ktor.websocket.serialization.*
import event.ChatEvent
import korlibs.event.Key
import korlibs.image.color.Colors
import korlibs.image.text.HorizontalAlign
import korlibs.image.text.TextAlignment
import korlibs.io.lang.Cancellable
import korlibs.korge.annotations.KorgeExperimental
import korlibs.korge.input.*
import korlibs.korge.style.styles
Expand All @@ -14,18 +12,14 @@ import korlibs.korge.ui.*
import korlibs.korge.view.*
import korlibs.korge.view.align.*
import korlibs.math.geom.Size
import korlibs.math.geom.bezier.Bezier
import kotlinx.uuid.UUID
import network.*
import scene.styler
import sceneContainer
import ui.custom.customUiButton
import ui.custom.customUiScrollable
import ui.custom.customUiText
import ui.custom.customUiTextInput
import ui.custom.*
import util.ColorPalette
import util.launchNow
import kotlin.math.abs
import websocket.sendToServer
import kotlin.math.max

@OptIn(KorgeExperimental::class)
suspend fun waitingRoom(room: UUID) {
Expand All @@ -35,10 +29,13 @@ suspend fun waitingRoom(room: UUID) {
val padding = 25f
val sidebarSize = Size(sceneContainer.width / 3.5f, sceneContainer.height)
styles(styler)
uiText(getRoomName(room)).position(padding, padding)
val belowElementHeight = sceneContainer.width / 25f
val leaveButton = Size(belowElementHeight*1.75f, belowElementHeight)
val inputBarSize = Size(sceneContainer.width - sidebarSize.width - padding*2 - leaveButton.width - padding*2, belowElementHeight)
val titleSize = Size(sceneContainer.width - sidebarSize.width, belowElementHeight * 1.5f)
val title = uiContainer(size = titleSize) {
uiText(getRoomName(room)).centerYOn(this)
}.position(padding, padding)
customUiButton(size = leaveButton) {
val back = solidRect(size, color = ColorPalette.out).centerOn(this)
var isDone = false
Expand All @@ -54,15 +51,34 @@ suspend fun waitingRoom(room: UUID) {
positionY(sceneContainer.height - padding - size.height)
}
lateinit var chats: View
customUiScrollable(size = inputBarSize) {
lateinit var scroll: CustomUIScrollable
val chatSize = Size(inputBarSize.width, sceneContainer.height - titleSize.height - inputBarSize.height - padding * 3)
customUiScrollable(cache = false, size = chatSize) {
scroll = it
it.positionX(padding + leaveButton.width + padding)
it.positionY(padding + titleSize.height + padding)
it.backgroundColor = Colors.TRANSPARENT
uiVerticalStack {
lateinit var space: View
uiVerticalStack(width = size.width, padding = padding, adjustSize = false) {
chats = this
uiSpacing(Size(1f, padding))
space = uiSpacing(Size(size.width, chatSize.height))
styles(styler)
styles {
textAlignment = TextAlignment.MIDDLE_LEFT
}
scroll.scrollBarAlpha = 0f
scroll.horizontal.view.visible = false
scroll.scrollTopRatio = 1f
onEvent(ChatEvent) { event ->
println("chat")
val (username, message) = event.chat
val chat = uiText("<$username> $message")
space.height = max(0f, space.height - chat.height)
scroll.scrollTopRatio = 1f
}
}
}
uiContainer {
uiContainer {
val input = customUiTextInput(size = inputBarSize.minus(Size(padding/2, 0f))) {
text = " "
styles { textAlignment = TextAlignment.MIDDLE_LEFT }
Expand All @@ -71,7 +87,7 @@ suspend fun waitingRoom(room: UUID) {
positionX(padding/2)
keys {
down(Key.ENTER) {
send(ClientPacket.CHAT, text)
sendToServer(ClientPacket.CHAT, text)
text = " "
}
}
Expand Down
51 changes: 51 additions & 0 deletions client/src/commonMain/kotlin/websocket/websocket.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package websocket

import event.ChatEvent
import io.ktor.util.*
import io.ktor.util.reflect.*
import io.ktor.utils.io.charsets.*
import korlibs.io.net.ws.WebSocketClient
import kotlinx.coroutines.Job
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import network.*
import sceneContainer
import util.launchNow

var _websocketClient: WebSocketClient? = null
suspend fun websocketClient(): WebSocketClient {
if (_websocketClient === null) {
_websocketClient = newWebsocketClient()
}
return _websocketClient!!
}
suspend fun newWebsocketClient() = WebSocketClient(currentUrl.httpToWs())

@OptIn(InternalAPI::class)
suspend inline fun <reified T> sendToServer(packet: Enum<*>, t: T) {
val packetFrame = PacketFrame(packet.ordinal, sessionUUID, Json.encodeToString<T>(t))
websocketClient().send(Json.encodeToString(packetFrame))
}


suspend fun startWebSocket(): Job {
return launchNow {
websocketClient().onStringMessage {
println("Aasdfasdfasdfasdf")
val packetFrame = Json.decodeFromString<PacketFrame>(it)
val serverPacket = ServerPacket.values()[packetFrame.type]
val packetController = serverPacket(serverPacket)
launchNow {
val data = decode(packetFrame.data, packetController.typeInfo)!!
packetController.invoke(data)
}
}
}
}

@Suppress("UNCHECKED_CAST")
fun serverPacket(serverPacket: ServerPacket): PacketController<Any> = when(serverPacket) {
ServerPacket.CHAT -> packet<Chat> {
sceneContainer.dispatch(ChatEvent(it))
}
} as PacketController<Any>
7 changes: 7 additions & 0 deletions server/src/main/kotlin/application/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package application
import application.configuration.configureAuthentication
import application.configuration.configureDatabase
import application.configuration.environment
import io.ktor.client.engine.*
import io.ktor.client.engine.cio.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
Expand All @@ -12,6 +14,11 @@ import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.routing.*
import network.ClientEngineFactory
import org.koin.core.context.startKoin
import org.koin.dsl.bind
import org.koin.mp.KoinPlatform.getKoin
import org.koin.mp.KoinPlatform.startKoin

val server = embeddedServer(Netty, environment)

Expand Down
49 changes: 21 additions & 28 deletions server/src/main/kotlin/application/websocket.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,23 @@ import io.ktor.serialization.kotlinx.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.util.*
import io.ktor.util.reflect.*
import io.ktor.websocket.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import io.ktor.websocket.serialization.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.uuid.UUID
import model.Room
import model.Rooms
import model.Session
import model.Sessions
import network.PacketController
import network.PacketFrame
import network.ClientPacket
import kotlinx.uuid.generateUUID
import model.*
import network.*
import network.ClientPacket.CHAT
import network.ClientPacket.GET_ROOM_NUMBER
import network.packet
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.Duration
import kotlin.reflect.KClass
import kotlin.reflect.KTypeProjection
import kotlin.reflect.full.allSupertypes
import kotlin.reflect.full.createType
import kotlin.reflect.javaType

val serialFormat = Json
val serverUUID = UUID.generateUUID()

fun Application.configureWebsocket() {
install(WebSockets) {
Expand All @@ -43,35 +33,38 @@ fun Application.configureWebsocket() {
webSocket {
while (true) {
val packet = receiveDeserialized<PacketFrame>()
val clientPacketCT = serverPacket(ClientPacket.values()[packet.type])
val clientPacket = ClientPacket.values()[packet.type]
val packetController = serverPacket(this, packet.session, clientPacket)
transaction { Session.find(Sessions.id eq packet.session).first() }
clientPacketCT.invoke(clientPacketCT.decode(packet.data)!!)
packetController.invoke(decode(packet.data, packetController.typeInfo)!!)
}
}
}
}

@OptIn(InternalSerializationApi::class, ExperimentalStdlibApi::class, ExperimentalSerializationApi::class)
private fun PacketController<*>.decode(data: String): Any? {
val serializer = serialFormat.serializersModule.serializerForTypeInfo(typeInfo)
@Suppress("UNCHECKED_CAST")
return serialFormat.decodeFromString(serializer as KSerializer<Any?>, data)
}

@Suppress("UNCHECKED_CAST")
fun serverPacket(clientPacket: ClientPacket): PacketController<Any> = when(clientPacket) {
suspend fun serverPacket(websocket: DefaultWebSocketSession, uuid: UUID, clientPacket: ClientPacket): PacketController<Any> = when(clientPacket) {
GET_ROOM_NUMBER -> packet<UUID> {
transaction { Room.find(Rooms.id eq it).first().name }
}
ClientPacket.LEAVE_ROOM -> packet {

}
CHAT -> packet<String> {
println(it)
val player = transaction { Player.find(Players.id eq getPlayerBySession(uuid)).first().name }
websocket.sendToClient(ServerPacket.CHAT, Chat(player, it))
}
} as PacketController<Any>

inline fun <reified T : Any> transactionPacket(crossinline code: (T) -> Any) = object : PacketController<T> {
override fun invoke(t: T) = transaction { code(t) }
override suspend fun invoke(t: T): Any = transaction { code(t) }
override val typeInfo: TypeInfo = typeInfo<T>()
}


@OptIn(InternalAPI::class)
suspend inline fun <reified T> DefaultWebSocketSession.sendToClient(packet: Enum<*>, t: T) {
val packetFrame = PacketFrame(packet.ordinal, serverUUID, Json.encodeToString<T>(t))
sendSerializedBase(packetFrame, typeInfo<PacketFrame>(), converter, Charsets.UTF_8)
}
15 changes: 15 additions & 0 deletions shared/src/commonMain/kotlin/event/AuditEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package event

import korlibs.event.*
import korlibs.math.geom.*
import korlibs.time.*
import network.Chat

class ChatEvent(
val chat: Chat,
) : Event(), TEvent<ChatEvent> {
companion object : EventType<ChatEvent>
override val type: EventType<ChatEvent> get() = ChatEvent

override fun toString(): String = "ChatEvent()"
}
6 changes: 6 additions & 0 deletions shared/src/commonMain/kotlin/network/Chat.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package network

import kotlinx.serialization.Serializable

@Serializable
data class Chat(val username: String, val message: String)
21 changes: 6 additions & 15 deletions shared/src/commonMain/kotlin/network/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,23 @@ import io.ktor.util.reflect.*
import io.ktor.utils.io.charsets.*
import io.ktor.websocket.*
import io.ktor.websocket.serialization.*
import korlibs.io.lang.UTF8
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.Json.Default.encodeToString
import kotlinx.uuid.UUID
import kotlinx.uuid.generateUUID
import org.koin.mp.KoinPlatform

val currentUrl get() = KoinPlatform.getKoin().get<URLProvider>().url
val clientEngine get() = KoinPlatform.getKoin().get<ClientEngineFactory>().getEngine()

private var clientInst: HttpClient? = null
private var semaphore = false
lateinit var websocket: WebSocketSession
suspend fun websocket(): WebSocketSession = client().webSocketSession(currentUrl.httpToWs())
interface URLProvider { val url: String }
interface ClientEngineFactory { fun getEngine(): HttpClientEngineFactory<HttpClientEngineConfig> }

val converter = KotlinxWebsocketSerializationConverter(Json)

@OptIn(InternalAPI::class)
suspend inline fun <reified T> send(clientPacket: ClientPacket, t: T) {
val packetFrame = PacketFrame(clientPacket.ordinal, sessionUUID, Json.encodeToString<T>(t))
websocket.sendSerializedBase(packetFrame, typeInfo<PacketFrame>(), converter, Charsets.UTF_8)
}

suspend inline fun <reified T> sendHttp(path: String, body: T, auth: Boolean = true) =
client().post("$currentUrl/$path") {
if (auth) basicAuth(username, sessionId)
Expand Down Expand Up @@ -70,10 +63,7 @@ private suspend fun initializeClient() = run {
json()
}
}
clientInst!!.also {
login()
websocket = it.webSocketSession(currentUrl.httpToWs())
}
login()
}

fun String.httpToWs(): String {
Expand All @@ -84,4 +74,5 @@ fun String.httpToWs(): String {
false -> "ws://${currentUrl.substring(http.length)}"
null -> "ws://$currentUrl"
}
}
}

Loading

0 comments on commit 93bc53c

Please sign in to comment.