Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repositories {
}

dependencies {
protobuf("gg.grounds:library-grpc-contracts-player:0.3.0")
protobuf("gg.grounds:library-grpc-contracts-player:0.4.0")

testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.4")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package gg.grounds.player.presence

import gg.grounds.grpc.player.CountPlayersByServerReply
import gg.grounds.grpc.player.CountPlayersByServerRequest
import gg.grounds.grpc.player.GetPlayerSessionRequest
import gg.grounds.grpc.player.PlayerHeartbeatBatchReply
import gg.grounds.grpc.player.PlayerHeartbeatBatchRequest
Expand Down Expand Up @@ -124,6 +126,16 @@ private constructor(
}
}

fun countPlayersByServer(): CountPlayersByServerReply? {
return try {
stub
.withDeadlineAfter(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.countPlayersByServer(CountPlayersByServerRequest.newBuilder().build())
} catch (e: RuntimeException) {
null
}
}

fun updateServer(playerId: UUID, serverName: String): Boolean {
return try {
stub
Expand Down
5 changes: 4 additions & 1 deletion velocity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ dependencies {
implementation(project(":common"))
// plugin-proxy owns the ProxyServiceRegistry at runtime — compileOnly, never shaded, or the
// registry this plugin writes into would be a different class from the one chat/social read.
compileOnly("gg.grounds:plugin-proxy-api:0.1.0")
compileOnly("gg.grounds:plugin-proxy-api:0.2.0")
implementation("tools.jackson.dataformat:jackson-dataformat-yaml:3.0.4")
implementation("tools.jackson.module:jackson-module-kotlin:3.0.4")
implementation("io.grpc:grpc-netty-shaded:1.78.0")

// compileOnly above is not visible to tests; PlayerSessionQueryImplTest needs the interface's
// types.
testImplementation("gg.grounds:plugin-proxy-api:0.2.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.4")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gg.grounds.presence

import gg.grounds.grpc.player.CountPlayersByServerReply
import gg.grounds.grpc.player.PlayerLogoutReply
import gg.grounds.grpc.player.PlayerSessionInfo
import gg.grounds.player.presence.GrpcPlayerPresenceClient
Expand Down Expand Up @@ -57,6 +58,14 @@ class PlayerPresenceService : AutoCloseable {
}
}

fun countPlayersByServer(): CountPlayersByServerReply? {
return try {
client.countPlayersByServer()
} catch (e: RuntimeException) {
null
}
}

fun updateServer(playerId: UUID, serverName: String): Boolean {
return try {
client.updateServer(playerId, serverName)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package gg.grounds.presence

import gg.grounds.grpc.player.CountPlayersByServerReply
import gg.grounds.proxy.api.NetworkPlayerCounts
import gg.grounds.proxy.api.PlayerSessionInfo
import gg.grounds.proxy.api.PlayerSessionQuery
import java.util.UUID
Expand All @@ -23,6 +25,9 @@ class PlayerSessionQueryImpl(private val presenceService: PlayerPresenceService)
override fun suggestNames(prefix: String, limit: Int): List<String> =
presenceService.suggestNames(prefix, limit)

override fun countPlayersByServer(): NetworkPlayerCounts? =
presenceService.countPlayersByServer()?.let(::toNetworkPlayerCounts)

/**
* A session with no usable id or name tells the caller nothing — drop it rather than
* half-answer.
Expand All @@ -38,4 +43,14 @@ class PlayerSessionQueryImpl(private val presenceService: PlayerPresenceService)
connectedAt = session.connectedAtMillis,
)
}

/**
* `servers` has one row per occupied backend server — a server nobody is on is absent, not a
* zero entry.
*/
internal fun toNetworkPlayerCounts(reply: CountPlayersByServerReply): NetworkPlayerCounts =
NetworkPlayerCounts(
byServer = reply.serversList.associate { it.serverName to it.players },
total = reply.total,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gg.grounds.presence

import gg.grounds.grpc.player.CountPlayersByServerReply
import gg.grounds.grpc.player.ServerPlayerCount
import java.net.ServerSocket
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test

class PlayerSessionQueryImplTest {

@Test
fun countPlayersByServerMapsServersAndCarriesTotal() {
val reply =
CountPlayersByServerReply.newBuilder()
.addServers(
ServerPlayerCount.newBuilder().setServerName("lobby-1").setPlayers(2).build()
)
.addServers(
ServerPlayerCount.newBuilder().setServerName("lobby-2").setPlayers(5).build()
)
.setTotal(8)
.build()

val counts = PlayerSessionQueryImpl(PlayerPresenceService()).toNetworkPlayerCounts(reply)

assertEquals(mapOf("lobby-1" to 2, "lobby-2" to 5), counts.byServer)
assertEquals(8, counts.total)
}

@Test
fun countPlayersByServerReturnsNullWhenPresenceServiceIsUnavailable() {
val presenceService = PlayerPresenceService()
val unusedPort = ServerSocket(0).use { it.localPort }
presenceService.configure("localhost:$unusedPort")

try {
assertNull(PlayerSessionQueryImpl(presenceService).countPlayersByServer())
} finally {
presenceService.close()
}
}
}
Loading