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

Commit 6aa3d22

Browse files
committed
feat: implement Redis event serialization, dynamic command support, and enhanced player listing
- Introduced `FasterXmlJsonOps` for efficient JSON operations with `DynamicOps`. - Added Redis event handling with `RedisEventSerializer` and `RedisEventDeserializer`. - Implemented `RedisEventDiscovererRegister` and `RedisEventRegistry` for Redis event discovery and registration. - Extended `AbstractConsoleCommand` with a generic `.execute()` function for dynamic command handling. - Added support for `list`, `group`, and `server` commands via `ListCommand`. - Enhanced player listing functionality in `GlistCommandExecutor`. - Added Cloud group and server argument types for improved command parsing. - Integrated Redis connection configuration in `ConnectionConfig`. - Added `CommandExecutionScope` for better coroutine handling in commands.
1 parent 9e86739 commit 6aa3d22

File tree

33 files changed

+1162
-93
lines changed

33 files changed

+1162
-93
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ spring-web = { module = "org.springframework:spring-web" }
6969
flyway-core = { module = "org.flywaydb:flyway-core" }
7070
flyway-mysql = { module = "org.flywaydb:flyway-mysql" }
7171
spring-instrument = { module = "org.springframework:spring-instrument" }
72+
spring-boot-starter-data-redis-reactive = { module = "org.springframework.boot:spring-boot-starter-data-redis-reactive" }
7273
kotlin-byte-buf-serializer = { module = "dev.slne.surf:kotlin-byte-buf-serializer", version.ref = "kotlin-byte-buf-serializer" }
7374
voicechat-api = { module = "de.maxhenkel.voicechat:voicechat-api", version.ref = "voicechat-api" }
7475
discord-webhooks = { module = "com.github.BinaryWriter:discord-webhooks", version.ref = "discord-webhooks" }

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ interface CloudServer : CommonCloudServer {
3636
CloudServerManager.retrieveServerByName(name) as? CloudServer
3737

3838
fun all() = CloudServerManager.retrieveServers()
39+
fun inGroup(group: String) = CloudServerManager.retrieveServerList(group)
3940
}
4041
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ interface CloudServerManager {
4242
fun retrieveServerByName(name: String): CommonCloudServer?
4343

4444
fun retrieveServersInGroup(group: String): ObjectList<out CommonCloudServer>
45+
fun retrieveServerList(group: String): ObjectList<CloudServer>
46+
fun retrieveProxyList(group: String): ObjectList<ProxyCloudServer>
47+
4548

4649
@InternalApi
4750
fun existsServerGroup(name: String): Boolean

surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/command/AbstractConsoleCommand.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package dev.slne.surf.cloud.api.server.command
22

3+
import com.mojang.brigadier.Command
34
import com.mojang.brigadier.CommandDispatcher
45
import com.mojang.brigadier.arguments.ArgumentType
56
import com.mojang.brigadier.builder.ArgumentBuilder
67
import com.mojang.brigadier.builder.LiteralArgumentBuilder
78
import com.mojang.brigadier.builder.RequiredArgumentBuilder
9+
import com.mojang.brigadier.context.CommandContext
810
import org.springframework.stereotype.Component
911

1012
@Component
@@ -14,6 +16,13 @@ annotation class ConsoleCommand
1416

1517
abstract class AbstractConsoleCommand {
1618
abstract fun register(dispatcher: CommandDispatcher<CommandSource>)
19+
20+
fun <S, T : ArgumentBuilder<S, T>> ArgumentBuilder<S, T>.execute(run: (ctx: CommandContext<S>) -> Unit): T =
21+
executes {
22+
run(it)
23+
Command.SINGLE_SUCCESS
24+
}
25+
1726
}
1827

1928
inline fun <S> AbstractConsoleCommand.literal(

surf-cloud-api/surf-cloud-api-server/src/main/kotlin/dev/slne/surf/cloud/api/server/command/CommandSource.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package dev.slne.surf.cloud.api.server.command
22

33
import com.mojang.brigadier.exceptions.CommandExceptionType
44
import dev.slne.surf.surfapi.core.api.messages.Colors
5+
import net.kyori.adventure.audience.Audience
6+
import net.kyori.adventure.audience.MessageType
7+
import net.kyori.adventure.identity.Identity
58
import net.kyori.adventure.text.Component
69
import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer
710

811
class CommandSource(
912
silent: Boolean = false,
1013
callback: CommandResultCallback = CommandResultCallback.EMPTY
11-
) : ExecutionCommandSource<CommandSource> {
14+
) : ExecutionCommandSource<CommandSource>, Audience {
1215

1316
override var callback = callback
1417
private set
@@ -33,7 +36,7 @@ class CommandSource(
3336
}
3437
}
3538

36-
fun sendMessage(message: Component) {
39+
override fun sendMessage(message: Component) {
3740
if (!silent) {
3841
println(ANSIComponentSerializer.ansi().serialize(message))
3942
}
@@ -58,6 +61,12 @@ class CommandSource(
5861
}
5962
}
6063

64+
@Deprecated("Deprecated in Java", level = DeprecationLevel.ERROR)
65+
@Suppress("DEPRECATION")
66+
override fun sendMessage(source: Identity, message: Component, type: MessageType) {
67+
sendMessage(message)
68+
}
69+
6170
private fun copy(
6271
silent: Boolean = this.silent,
6372
callback: CommandResultCallback = this.callback
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package dev.slne.surf.cloud.api.server.command.argument
2+
3+
import com.mojang.brigadier.LiteralMessage
4+
import com.mojang.brigadier.StringReader
5+
import com.mojang.brigadier.arguments.ArgumentType
6+
import com.mojang.brigadier.context.CommandContext
7+
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
8+
import com.mojang.brigadier.suggestion.Suggestions
9+
import com.mojang.brigadier.suggestion.SuggestionsBuilder
10+
import dev.slne.surf.cloud.api.common.server.CloudServerManager
11+
import dev.slne.surf.cloud.api.server.command.ArgumentSuggestion
12+
import java.util.concurrent.CompletableFuture
13+
14+
class CloudGroupArgumentType private constructor(): ArgumentType<String> {
15+
companion object {
16+
val GROUP_NOT_FOUND =
17+
DynamicCommandExceptionType { group -> LiteralMessage("Server group '$group' not found or no servers in this group are online!") }
18+
19+
fun group(): CloudGroupArgumentType {
20+
return CloudGroupArgumentType()
21+
}
22+
23+
fun getGroup(context: CommandContext<*>, name: String): String {
24+
return context.getArgument(name, String::class.java)
25+
}
26+
}
27+
28+
override fun parse(reader: StringReader): String {
29+
val group = reader.readUnquotedString()
30+
31+
if (!CloudServerManager.existsServerGroup(group)) {
32+
throw GROUP_NOT_FOUND.create(group)
33+
}
34+
35+
return group
36+
}
37+
38+
override fun <S : Any> listSuggestions(
39+
context: CommandContext<S>,
40+
builder: SuggestionsBuilder
41+
): CompletableFuture<Suggestions> {
42+
val groups = CloudServerManager.retrieveAllServers()
43+
.filter { it.group.isNotEmpty() }
44+
.map { it.group }
45+
.distinct()
46+
47+
return ArgumentSuggestion.suggestStrings(groups, builder)
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package dev.slne.surf.cloud.api.server.command.argument
2+
3+
import com.mojang.brigadier.LiteralMessage
4+
import com.mojang.brigadier.StringReader
5+
import com.mojang.brigadier.arguments.ArgumentType
6+
import com.mojang.brigadier.context.CommandContext
7+
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
8+
import com.mojang.brigadier.suggestion.Suggestions
9+
import com.mojang.brigadier.suggestion.SuggestionsBuilder
10+
import dev.slne.surf.cloud.api.common.server.CloudServer
11+
import dev.slne.surf.cloud.api.server.command.ArgumentSuggestion
12+
import java.util.concurrent.CompletableFuture
13+
14+
class CloudServerArgumentType private constructor() : ArgumentType<CloudServer> {
15+
companion object {
16+
val SERVER_NOT_FOUND =
17+
DynamicCommandExceptionType { group -> LiteralMessage("Server '$group' not found!") }
18+
19+
fun server(): CloudServerArgumentType {
20+
return CloudServerArgumentType()
21+
}
22+
23+
fun getServer(context: CommandContext<*>, name: String): CloudServer {
24+
return context.getArgument(name, CloudServer::class.java)
25+
}
26+
}
27+
28+
override fun parse(reader: StringReader): CloudServer {
29+
val serverName = reader.readUnquotedString()
30+
val server = CloudServer[serverName]
31+
32+
if (server == null) {
33+
throw SERVER_NOT_FOUND.create(serverName)
34+
}
35+
36+
return server
37+
}
38+
39+
override fun <S : Any> listSuggestions(
40+
context: CommandContext<S>,
41+
builder: SuggestionsBuilder
42+
): CompletableFuture<Suggestions> {
43+
val serverNames = CloudServer.all().map(CloudServer::name)
44+
return ArgumentSuggestion.suggestStrings(serverNames, builder)
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dev.slne.surf.cloud.api.server.redis
2+
3+
import com.mojang.datafixers.util.Pair
4+
import com.mojang.serialization.Codec
5+
import com.mojang.serialization.DataResult
6+
import com.mojang.serialization.DynamicOps
7+
import kotlinx.serialization.KSerializer
8+
import kotlinx.serialization.SerializationException
9+
import kotlinx.serialization.json.Json
10+
11+
class KotlinJsonSerializationCodec<A>(
12+
private val serializer: KSerializer<A>,
13+
private val json: Json
14+
) : Codec<A> {
15+
private val codec = Codec.STRING.flatXmap<A>({ json ->
16+
try {
17+
DataResult.success(this.json.decodeFromString(serializer, json))
18+
} catch (e: SerializationException) {
19+
DataResult.error { "Unable to deserialize JSON: $json: ${e.message}" }
20+
} catch (e: IllegalArgumentException) {
21+
DataResult.error { "Cannot represent JSON as object: $json: ${e.message}" }
22+
}
23+
24+
}, { obj ->
25+
try {
26+
DataResult.success(json.encodeToString(serializer, obj))
27+
} catch (e: SerializationException) {
28+
DataResult.error { "Unable to serialize object: $obj: ${e.message}" }
29+
}
30+
})
31+
32+
33+
override fun <T : Any> encode(
34+
input: A,
35+
ops: DynamicOps<T>,
36+
prefix: T
37+
): DataResult<T> {
38+
return codec.encode(input, ops, prefix)
39+
}
40+
41+
override fun <T : Any> decode(
42+
ops: DynamicOps<T>,
43+
input: T
44+
): DataResult<Pair<A, T>> {
45+
return codec.decode(ops, input)
46+
}
47+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package dev.slne.surf.cloud.api.server.redis
2+
3+
@Target(AnnotationTarget.PROPERTY)
4+
@Retention(AnnotationRetention.RUNTIME)
5+
annotation class RedisCodec
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dev.slne.surf.cloud.api.server.redis
2+
3+
import dev.slne.surf.cloud.api.common.util.findAnnotation
4+
5+
abstract class RedisEvent {
6+
@delegate:Transient
7+
val meta by lazy {
8+
this::class.findAnnotation<RedisEventMeta>()
9+
?: error("@RedisEventMeta annotation is missing on ${this::class.qualifiedName}")
10+
}
11+
12+
fun publish() {
13+
RedisEventBus.publish(this)
14+
}
15+
16+
override fun toString(): String {
17+
return "RedisEvent(meta=$meta)"
18+
}
19+
}

0 commit comments

Comments
 (0)