-
Notifications
You must be signed in to change notification settings - Fork 0
Fix/sync initial players #138
Conversation
- Added new netty packets for syncing player data: start, chunk, and end packets for both hydration and large PDC synchronization. - Introduced caching for pending PDCs using Caffeine with a two-minute expiration. - Refactored player server and proxy management APIs to use nullable parameters for enhanced flexibility. - Updated synchronization protocols to include newly added codec streams. - Improved handling logic in packet listeners for hydration and PDC chunks with fail-safe checks. - Refactored existing player creation methods to consolidate logic and improve initialization consistency.
…nchronizeUserTask
- Replaced synchronized map-based queue with `ConcurrentLinkedQueue` for improved concurrency. - Refactored `_connection` setter logic to delegate queue draining to a dedicated method. - Introduced `QueuedPacket` data class for cleaner queue operation. - Simplified `fireAndForget` and `fire` methods to enhance readability and maintainability.
- Renamed `BeforeStartTaskScope` to `SynchronizeTasksScope` for clarity. - Replaced `Dispatchers.IO` with `Dispatchers.Default` in task scope implementation for better task scheduling. - Updated synchronization task executions to use `fireAndForget` for reduced overhead. - Added new synchronization tasks: `SetVelocitySecretTask` and `SynchronizeServersTask` for modularity and reusability.
- Added `SynchronizePlayerPunishmentManagers` task for synchronizing player mute data. - Introduced `ClientboundSynchronizePlayerMutes` packet with supporting `STREAM_CODEC`. - Updated synchronization protocols to include new packet type. - Expanded punishment manager with methods to manage raw cached mutes. - Enhanced server-client packet listeners to handle and process mute synchronization.
… ConcurrentLinkedQueue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the initial player synchronization system to properly sync existing players when a new server connects to the standalone server. The key changes include modifying player manager APIs to use nullable proxy/server names, implementing a new chunked player hydration protocol with support for large persistent data containers, and synchronizing player punishment data (mutes).
- Refactored
createPlayerAPI to accept nullableproxyNameandserverNameparameters instead of a boolean flag - Implemented new player hydration protocol with chunking for large data transfers
- Added synchronization tasks for servers, players, and punishment data during initial connection
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| surf-cloud-velocity/.../VelocityCloudPlayerManagerImpl.kt | Updated createPlayer signature to use nullable proxy/server names |
| surf-cloud-bukkit/.../BukkitCloudPlayerManagerImpl.kt | Updated createPlayer signature to use nullable proxy/server names |
| surf-cloud-standalone/.../StandaloneCloudPlayerManagerImpl.kt | Refactored createPlayer with improved null handling and removed serverName parameter from removeProxyServer/removeServer |
| surf-cloud-standalone/.../SynchronizeUserTask.kt | New task implementing chunked player hydration with large PDC support |
| surf-cloud-standalone/.../SynchronizeServersTask.kt | New task to sync server list during initial connection |
| surf-cloud-standalone/.../SynchronizeRegistriesTask.kt | Updated to use fireAndForget instead of direct connection send |
| surf-cloud-standalone/.../SynchronizePlayerPunishmentManagers.kt | New task to sync cached player mutes |
| surf-cloud-standalone/.../SetVelocitySecretTask.kt | Extracted Velocity secret setting into separate task |
| surf-cloud-standalone/.../ServerSynchronizingPacketListenerImpl.kt | Updated to execute new synchronization tasks in refactored scope |
| surf-cloud-standalone/.../ByteCodeModifyingURLClassloader.kt | Replaced custom synchronized map with ConcurrentHashMap |
| surf-cloud-standalone/.../CoroutineManagerImpl.kt | Replaced custom synchronized map with ConcurrentHashMap and used compute for atomic operations |
| surf-cloud-core/.../CloudSynchronizeTaskManager.kt | Renamed scope from BeforeStartTaskScope to SynchronizeTasksScope |
| surf-cloud-core/.../CloudPlayerPunishmentManagerImpl.kt | Added methods for raw mute access and updating mutes from sync |
| surf-cloud-core/.../CommonCloudPlayerImpl.kt | Added utility methods to estimate and serialize PDC size |
| surf-cloud-core/.../CloudPlayerManagerImpl.kt | Updated API signatures and added createExistingPlayer helper for hydration |
| surf-cloud-core/.../SynchronizingProtocols.kt | Registered new player hydration and mute sync packets |
| surf-cloud-core/.../ClientboundSynchronizePlayerMutes.kt | New packet for syncing player mutes |
| surf-cloud-core/.../ClientboundSyncPlayerHydration*.kt | New packets for chunked player hydration protocol |
| surf-cloud-core/.../ClientboundSyncLargePlayerPersistentDataContainer*.kt | New packets for large PDC transfer |
| surf-cloud-core/.../ClientSynchronizingPacketListener.kt | Added handler methods for new synchronization packets |
| surf-cloud-core/.../TickablePacketListener.kt | Replaced synchronized list with ConcurrentLinkedQueue and added error handling |
| surf-cloud-core/.../RespondingPacketSendHandler.kt | Removed synchronization from respondingPackets map |
| surf-cloud-core/.../ConnectionImpl.kt | Removed extraneous blank line |
| surf-cloud-core/.../CommonNettyClientImpl.kt | Refactored packet queue to use ConcurrentLinkedQueue with double-check pattern |
| surf-cloud-core/.../scopes.kt | Renamed BeforeStartTaskScope to SynchronizeTasksScope and changed dispatcher to Default |
| surf-cloud-core/.../CommonClientCloudPlayerManagerImpl.kt | Updated removeProxyServer and removeServer to not require serverName parameter |
| surf-cloud-core/.../ClientSynchronizingPacketListenerImpl.kt | Implemented handlers for new player hydration and mute sync packets |
| private val log = logger() | ||
| private val respondingPackets = | ||
| mutableObject2ObjectMapOf<UUID, CompletableDeferred<ResponseNettyPacket>>().synchronize() | ||
| mutableObject2ObjectMapOf<UUID, CompletableDeferred<ResponseNettyPacket>>() |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The respondingPackets map is accessed from multiple methods (handleRead on line 41 and handleWrite on line 65) that can be called concurrently from Netty's event loop threads. The map should be thread-safe. Consider using ConcurrentHashMap instead of the unsynchronized mutableObject2ObjectMapOf.
| mutableObject2ObjectMapOf<UUID, CompletableDeferred<ResponseNettyPacket>>() | |
| ConcurrentHashMap<UUID, CompletableDeferred<ResponseNettyPacket>>() |
| it.proxyServerName = serverName | ||
| it.serverName = serverName |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proxy and server names are being swapped. Line 27 should set it.proxyServerName = proxyName and line 28 should set it.serverName = serverName. Currently both are incorrectly set to serverName.
| } else { | ||
| it.serverName = serverName | ||
| } | ||
| it.proxyServerName = serverName |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proxy and server names are being swapped. Line 27 should set it.proxyServerName = proxyName and line 28 should set it.serverName = serverName. Currently both are incorrectly set to serverName.
| it.proxyServerName = serverName | |
| it.proxyServerName = proxyName |
| val server = proxyName.toServer() ?: log.atWarning() | ||
| .log("Could not find proxy server with id $proxyName") | ||
| check(server is StandaloneProxyCloudServerImpl) { "Server with id $proxyName is not a proxy server but specified as proxy" } | ||
| it.proxyServer = server | ||
| } | ||
|
|
||
| if (serverName != null) { | ||
| val server = serverName.toServer() ?: log.atWarning() | ||
| .log("Could not find server with id $serverName") |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The toServer() call returns either a server object or null, but this code uses the elvis operator (?:) with a log statement that returns Unit. This means server will be Unit (not null) when the server is not found, causing the check on line 106 to fail with the wrong type error instead of properly handling the missing server case. The log call should be moved after the null check. Same issue exists on line 111.
| val server = proxyName.toServer() ?: log.atWarning() | |
| .log("Could not find proxy server with id $proxyName") | |
| check(server is StandaloneProxyCloudServerImpl) { "Server with id $proxyName is not a proxy server but specified as proxy" } | |
| it.proxyServer = server | |
| } | |
| if (serverName != null) { | |
| val server = serverName.toServer() ?: log.atWarning() | |
| .log("Could not find server with id $serverName") | |
| val server = proxyName.toServer() | |
| if (server == null) { | |
| log.atWarning().log("Could not find proxy server with id $proxyName") | |
| } | |
| check(server is StandaloneProxyCloudServerImpl) { "Server with id $proxyName is not a proxy server but specified as proxy" } | |
| it.proxyServer = server | |
| } | |
| if (serverName != null) { | |
| val server = serverName.toServer() | |
| if (server == null) { | |
| log.atWarning().log("Could not find server with id $serverName") | |
| } |
| val server = proxyName.toServer() ?: log.atWarning() | ||
| .log("Could not find proxy server with id $proxyName") | ||
| check(server is StandaloneProxyCloudServerImpl) { "Server with id $proxyName is not a proxy server but specified as proxy" } | ||
| it.proxyServer = server | ||
| } | ||
|
|
||
| if (serverName != null) { | ||
| val server = serverName.toServer() ?: log.atWarning() | ||
| .log("Could not find server with id $serverName") |
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The toServer() call returns either a server object or null, but this code uses the elvis operator (?:) with a log statement that returns Unit. This means server will be Unit (not null) when the server is not found, causing the check on line 113 to fail with the wrong type error instead of properly handling the missing server case. The log call should be moved after the null check.
| val server = proxyName.toServer() ?: log.atWarning() | |
| .log("Could not find proxy server with id $proxyName") | |
| check(server is StandaloneProxyCloudServerImpl) { "Server with id $proxyName is not a proxy server but specified as proxy" } | |
| it.proxyServer = server | |
| } | |
| if (serverName != null) { | |
| val server = serverName.toServer() ?: log.atWarning() | |
| .log("Could not find server with id $serverName") | |
| val server = proxyName.toServer() | |
| if (server == null) { | |
| log.atWarning().log("Could not find proxy server with id $proxyName") | |
| } | |
| check(server is StandaloneProxyCloudServerImpl) { "Server with id $proxyName is not a proxy server but specified as proxy" } | |
| it.proxyServer = server | |
| } | |
| if (serverName != null) { | |
| val server = serverName.toServer() | |
| if (server == null) { | |
| log.atWarning().log("Could not find server with id $serverName") | |
| } |
Qodana for JVM540 new problems were found
☁️ View the detailed Qodana report Contact Qodana teamContact us at qodana-support@jetbrains.com
|
No description provided.