Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.

Commit 20c2219

Browse files
committed
refactor(CloudPlayerManagerImpl): replace player management with Caffeine cache for improved performance
1 parent b319d6e commit 20c2219

File tree

7 files changed

+150
-124
lines changed

7 files changed

+150
-124
lines changed

.idea/runConfigurations/Build_all.xml

Lines changed: 0 additions & 25 deletions
This file was deleted.

surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/util/util.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.slne.surf.cloud.api.common.util
22

3+
import com.sksamuel.aedile.core.Cache
34
import it.unimi.dsi.fastutil.objects.ObjectList
45
import kotlinx.coroutines.Deferred
56
import kotlinx.coroutines.async
@@ -165,3 +166,5 @@ operator fun AtomicBoolean.getValue(thisRef: Any?, property: KProperty<*>): Bool
165166
operator fun AtomicBoolean.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
166167
set(value)
167168
}
169+
170+
fun <K, V> Cache<K, V>.currentValues() = underlying().asMap().values.mapNotNull { it.getNow(null) }

surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/player/CloudPlayerManagerImpl.kt

Lines changed: 127 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package dev.slne.surf.cloud.core.common.player
22

3+
import com.github.benmanes.caffeine.cache.Caffeine
4+
import com.sksamuel.aedile.core.asCache
35
import dev.slne.surf.cloud.api.common.event.player.connection.CloudPlayerConnectToNetworkEvent
46
import dev.slne.surf.cloud.api.common.event.player.connection.CloudPlayerDisconnectFromNetworkEvent
57
import dev.slne.surf.cloud.api.common.player.CloudPlayer
@@ -8,33 +10,33 @@ import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask
810
import dev.slne.surf.cloud.api.common.server.UserList
911
import dev.slne.surf.cloud.api.common.server.UserListImpl
1012
import dev.slne.surf.cloud.api.common.util.TimeLogger
11-
import dev.slne.surf.cloud.api.common.util.mutableObject2ObjectMapOf
12-
import dev.slne.surf.cloud.api.common.util.synchronize
13+
import dev.slne.surf.cloud.api.common.util.currentValues
1314
import dev.slne.surf.cloud.core.common.spring.CloudLifecycleAware
1415
import dev.slne.surf.surfapi.core.api.util.logger
15-
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
16-
import kotlinx.coroutines.CompletableDeferred
17-
import kotlinx.coroutines.sync.Mutex
18-
import kotlinx.coroutines.sync.withLock
1916
import org.jetbrains.annotations.MustBeInvokedByOverriders
2017
import org.springframework.core.annotation.Order
2118
import org.springframework.stereotype.Component
19+
import java.io.Serial
2220
import java.net.Inet4Address
2321
import java.util.*
2422

2523
abstract class CloudPlayerManagerImpl<P : CommonCloudPlayerImpl> : CloudPlayerManager {
2624
private val log = logger()
27-
protected val players = mutableObject2ObjectMapOf<UUID, P>().synchronize()
28-
protected val creatingPlayers =
29-
mutableObject2ObjectMapOf<UUID, CompletableDeferred<P?>>().synchronize()
30-
protected val createMutex = Mutex()
25+
26+
protected val playerCache = Caffeine.newBuilder()
27+
.asCache<UUID, P>()
28+
29+
// protected val players = mutableObject2ObjectMapOf<UUID, P>().synchronize()
30+
// protected val creatingPlayers =
31+
// mutableObject2ObjectMapOf<UUID, CompletableDeferred<P?>>().synchronize()
32+
// protected val createMutex = Mutex()
3133

3234
override fun getPlayer(uuid: UUID?): P? {
33-
return players[uuid]
35+
return uuid?.let { playerCache.getOrNull(it) }
3436
}
3537

3638
override fun getPlayer(name: String): CloudPlayer? =
37-
players.values.find { it.name.equals(name, ignoreCase = true) }
39+
playerCache.currentValues().find { it.name.equals(name, ignoreCase = true) }
3840

3941
abstract suspend fun createPlayer(
4042
uuid: UUID,
@@ -53,18 +55,12 @@ abstract class CloudPlayerManagerImpl<P : CommonCloudPlayerImpl> : CloudPlayerMa
5355
abstract fun getProxyServerUid(player: P): Long?
5456
abstract fun getServerUid(player: P): Long?
5557

56-
private fun addPlayer(player: P) {
57-
players[player.uuid] = player
58-
}
59-
6058
override fun getOnlinePlayers(): UserList {
61-
return UserListImpl.of(players.values)
59+
return UserListImpl.of(playerCache.currentValues())
6260
}
6361

6462
protected inline fun forEachPlayer(action: (P) -> Unit) {
65-
val tempPlayers = Object2ObjectArrayMap(players)
66-
tempPlayers.values.forEach(action)
67-
tempPlayers.clear()
63+
playerCache.currentValues().forEach(action)
6864
}
6965

7066
/**
@@ -83,29 +79,58 @@ abstract class CloudPlayerManagerImpl<P : CommonCloudPlayerImpl> : CloudPlayerMa
8379
serverUid: Long,
8480
runPreJoinTasks: Boolean
8581
): PrePlayerJoinTask.Result {
86-
val (player, preJoinResult, created) = getOrCreatePlayerAtomically(
87-
uuid,
88-
name,
89-
proxy,
90-
ip,
91-
serverUid,
92-
runPreJoinTasks
93-
)
94-
95-
if (player == null) {
96-
return preJoinResult
97-
}
98-
99-
if (!created) {
82+
val existing = playerCache.getOrNull(uuid)
83+
if (existing != null) {
10084
if (proxy) {
101-
updateProxyServer(player, serverUid)
85+
updateProxyServer(existing, serverUid)
10286
} else {
103-
updateServer(player, serverUid)
87+
updateServer(existing, serverUid)
10488
}
105-
onServerConnect(uuid, player, serverUid)
89+
onServerConnect(uuid, existing, serverUid)
90+
return PrePlayerJoinTask.Result.ALLOWED
10691
}
10792

108-
return PrePlayerJoinTask.Result.ALLOWED
93+
return try {
94+
playerCache.get(uuid) {
95+
val newPlayer = createPlayer(uuid, name, proxy, ip, serverUid)
96+
97+
if (runPreJoinTasks) {
98+
val pre = preJoin(newPlayer)
99+
if (pre !is PrePlayerJoinTask.Result.ALLOWED) throw PreJoinDenied(pre)
100+
}
101+
onNetworkConnect(uuid, newPlayer)
102+
onServerConnect(uuid, newPlayer, serverUid)
103+
newPlayer
104+
}
105+
PrePlayerJoinTask.Result.ALLOWED
106+
} catch (e: PreJoinDenied) {
107+
e.result
108+
}
109+
110+
111+
// val (player, preJoinResult, created) = getOrCreatePlayerAtomically(
112+
// uuid,
113+
// name,
114+
// proxy,
115+
// ip,
116+
// serverUid,
117+
// runPreJoinTasks
118+
// )
119+
//
120+
// if (player == null) {
121+
// return preJoinResult
122+
// }
123+
//
124+
// if (!created) {
125+
// if (proxy) {
126+
// updateProxyServer(player, serverUid)
127+
// } else {
128+
// updateServer(player, serverUid)
129+
// }
130+
// onServerConnect(uuid, player, serverUid)
131+
// }
132+
//
133+
// return PrePlayerJoinTask.Result.ALLOWED
109134

110135

111136
// val player = players[uuid]
@@ -167,45 +192,45 @@ abstract class CloudPlayerManagerImpl<P : CommonCloudPlayerImpl> : CloudPlayerMa
167192
// return PrePlayerJoinTask.Result.ALLOWED
168193
}
169194

170-
private suspend fun getOrCreatePlayerAtomically(
171-
uuid: UUID,
172-
name: String,
173-
proxy: Boolean,
174-
ip: Inet4Address,
175-
serverUid: Long,
176-
runPreJoinTasks: Boolean
177-
): Triple<P?, PrePlayerJoinTask.Result, Boolean> {
178-
players[uuid]?.let { return Triple(it, PrePlayerJoinTask.Result.ALLOWED, false) }
179-
180-
var needsCreation = false
181-
val creatingPlayer = createMutex.withLock {
182-
creatingPlayers.computeIfAbsent(uuid) {
183-
needsCreation = true
184-
CompletableDeferred()
185-
}
186-
}
187-
188-
if (!creatingPlayer.isCompleted && needsCreation) {
189-
val newPlayer = createPlayer(uuid, name, proxy, ip, serverUid)
190-
191-
if (runPreJoinTasks) {
192-
val preJoinResult = preJoin(newPlayer)
193-
if (preJoinResult !is PrePlayerJoinTask.Result.ALLOWED) {
194-
creatingPlayer.complete(null)
195-
return Triple(null, preJoinResult, true)
196-
}
197-
}
198-
199-
addPlayer(newPlayer)
200-
creatingPlayers.remove(uuid)
201-
202-
onNetworkConnect(uuid, newPlayer)
203-
onServerConnect(uuid, newPlayer, serverUid)
204-
creatingPlayer.complete(newPlayer)
205-
}
206-
207-
return Triple(creatingPlayer.await(), PrePlayerJoinTask.Result.ALLOWED, true)
208-
}
195+
// private suspend fun getOrCreatePlayerAtomically(
196+
// uuid: UUID,
197+
// name: String,
198+
// proxy: Boolean,
199+
// ip: Inet4Address,
200+
// serverUid: Long,
201+
// runPreJoinTasks: Boolean
202+
// ): Triple<P?, PrePlayerJoinTask.Result, Boolean> {
203+
// players[uuid]?.let { return Triple(it, PrePlayerJoinTask.Result.ALLOWED, false) }
204+
//
205+
// var needsCreation = false
206+
// val creatingPlayer = createMutex.withLock {
207+
// creatingPlayers.computeIfAbsent(uuid) {
208+
// needsCreation = true
209+
// CompletableDeferred()
210+
// }
211+
// }
212+
//
213+
// if (!creatingPlayer.isCompleted && needsCreation) {
214+
// val newPlayer = createPlayer(uuid, name, proxy, ip, serverUid)
215+
//
216+
// if (runPreJoinTasks) {
217+
// val preJoinResult = preJoin(newPlayer)
218+
// if (preJoinResult !is PrePlayerJoinTask.Result.ALLOWED) {
219+
// creatingPlayer.complete(null)
220+
// return Triple(null, preJoinResult, true)
221+
// }
222+
// }
223+
//
224+
// addPlayer(newPlayer)
225+
// creatingPlayers.remove(uuid)
226+
//
227+
// onNetworkConnect(uuid, newPlayer)
228+
// onServerConnect(uuid, newPlayer, serverUid)
229+
// creatingPlayer.complete(newPlayer)
230+
// }
231+
//
232+
// return Triple(creatingPlayer.await(), PrePlayerJoinTask.Result.ALLOWED, true)
233+
// }
209234

210235
protected open suspend fun preJoin(player: P): PrePlayerJoinTask.Result {
211236
return PrePlayerJoinTask.Result.ALLOWED
@@ -220,20 +245,25 @@ abstract class CloudPlayerManagerImpl<P : CommonCloudPlayerImpl> : CloudPlayerMa
220245
* @param proxy A boolean indicating if the player was connected through a proxy.
221246
*/
222247
suspend fun updateOrRemoveOnDisconnect(uuid: UUID, serverUid: Long, proxy: Boolean) {
223-
val player = players[uuid] ?: return
224-
val oldProxy = getProxyServerUid(player)
225-
val oldServer = getServerUid(player)
248+
val player = playerCache.getIfPresent(uuid)
249+
if (player != null) {
250+
val oldProxy = getProxyServerUid(player)
251+
val oldServer = getServerUid(player)
252+
if (proxy) {
253+
removeProxyServer(player, serverUid)
254+
} else {
255+
removeServer(player, serverUid)
256+
}
257+
onServerDisconnect(uuid, player, serverUid)
226258

227-
if (proxy) {
228-
removeProxyServer(player, serverUid)
259+
if (!player.connected) {
260+
playerCache.invalidate(uuid)
261+
onNetworkDisconnect(uuid, player, oldProxy, oldServer)
262+
}
229263
} else {
230-
removeServer(player, serverUid)
231-
}
232-
onServerDisconnect(uuid, player, serverUid)
233-
234-
if (!player.connected) {
235-
players.remove(uuid)
236-
onNetworkDisconnect(uuid, player, oldProxy, oldServer)
264+
log.atWarning()
265+
.log("Player with UUID $uuid not found during disconnect handling")
266+
playerCache.invalidate(uuid)
237267
}
238268
}
239269

@@ -268,6 +298,14 @@ abstract class CloudPlayerManagerImpl<P : CommonCloudPlayerImpl> : CloudPlayerMa
268298
}
269299

270300
open fun terminate() {}
301+
302+
303+
private class PreJoinDenied(val result: PrePlayerJoinTask.Result) : RuntimeException() {
304+
companion object {
305+
@Serial
306+
private const val serialVersionUID: Long = -5043277924406776272L
307+
}
308+
}
271309
}
272310

273311
val playerManagerImpl get() = CloudPlayerManager.instance as CloudPlayerManagerImpl<out CommonCloudPlayerImpl>

surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/PlayerConnectionLogger.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dev.slne.surf.cloud.standalone.player
22

33
import dev.slne.surf.cloud.api.common.event.CloudEventHandler
44
import dev.slne.surf.cloud.api.common.event.player.connection.CloudPlayerConnectToNetworkEvent
5+
import dev.slne.surf.cloud.api.common.event.player.connection.CloudPlayerDisconnectFromNetworkEvent
56
import dev.slne.surf.cloud.standalone.config.standaloneConfig
67
import dev.slne.surf.surfapi.core.api.util.logger
78
import org.springframework.stereotype.Component
@@ -20,4 +21,13 @@ class PlayerConnectionLogger {
2021
log.atInfo()
2122
.log("Player ${player.name} (${player.uuid}) connected to the network")
2223
}
24+
25+
@CloudEventHandler
26+
fun onCloudPlayerDisconnectFromNetwork(event: CloudPlayerDisconnectFromNetworkEvent) {
27+
if (!standaloneConfig.logging.logPlayerConnections) return
28+
val player = event.player as StandaloneCloudPlayerImpl
29+
30+
log.atInfo()
31+
.log("Player ${player.name} (${player.uuid}) disconnected from the network")
32+
}
2333
}

surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/player/StandaloneCloudPlayerManagerImpl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.google.auto.service.AutoService
44
import dev.slne.surf.cloud.api.common.player.CloudPlayerManager
55
import dev.slne.surf.cloud.api.common.player.OfflineCloudPlayer
66
import dev.slne.surf.cloud.api.common.player.task.PrePlayerJoinTask
7+
import dev.slne.surf.cloud.api.common.util.currentValues
78
import dev.slne.surf.cloud.api.server.export.PlayerDataExport
89
import dev.slne.surf.cloud.api.server.export.PlayerDataExportEmpty
910
import dev.slne.surf.cloud.core.common.coroutines.PlayerDataSaveScope
@@ -163,7 +164,7 @@ class StandaloneCloudPlayerManagerImpl : CloudPlayerManagerImpl<StandaloneCloudP
163164
return OfflineCloudPlayerImpl(uuid)
164165
}
165166

166-
fun getRawOnlinePlayers() = players.values.toList()
167+
fun getRawOnlinePlayers() = playerCache.currentValues()
167168

168169
override suspend fun onServerConnect(
169170
uuid: UUID,

surf-cloud-standalone/src/main/kotlin/dev/slne/surf/cloud/standalone/server/StandaloneCloudServerImpl.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,14 @@ class StandaloneCloudServerImpl(
3232
) : AbstractCloudServer(uid, group, name, playAddress, lobby), ServerCloudServer,
3333
CommonStandaloneServer by CommonStandaloneServerImpl() {
3434

35-
init {
36-
wrapper = this
37-
startCleanupTask()
38-
}
39-
4035
val connectingPlayers = Caffeine.newBuilder()
4136
.weakKeys()
4237
.build<StandaloneCloudPlayerImpl, Boolean>()
4338

39+
init {
40+
wrapper = this
41+
startCleanupTask()
42+
}
4443

4544
override val expectedPlayers: Int
4645
get() = (currentPlayerCount + connectingPlayers.estimatedSize()).toInt()

0 commit comments

Comments
 (0)