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

Commit ebe697a

Browse files
committed
feat: implement player cache system and synchronization protocols
- Added `CloudPlayerCacheImpl` as the core implementation of player caching. - Introduced various cache types: `CacheListImpl`, `CacheSetImpl`, `CacheMapImpl`, and `CacheStructuredImpl` for structured data handling. - Developed `EntryMeta` to manage cache entry metadata, supporting multiple entry types. - Implemented delta packets (`PlayerCacheListDeltaPacket`, `PlayerCacheSetDeltaPacket`, etc.) for partial synchronization. - Created full synchronization packets (`PlayerCacheFullSyncPacket`, `PlayerCacheRequestFullSyncPacket`) to support player state consistency. - Included `ChangeCounter` for tracking cache mutations and ensuring synchronization accuracy. - Refactored `AdventureKeySerializer` to streamline serialization logic.
1 parent b6dfb02 commit ebe697a

File tree

34 files changed

+1485
-11
lines changed

34 files changed

+1485
-11
lines changed
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
package dev.slne.surf.cloud.api.common.netty.network.codec
22

3+
import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf
4+
import io.netty.buffer.ByteBuf
5+
import io.netty.buffer.Unpooled
6+
37
@FunctionalInterface
48
fun interface StreamDecoder<I, T> {
59
fun decode(buf: I): T
6-
}
10+
}
11+
12+
inline fun <I, T> StreamDecoder<I, T>.decodeFromByteArray(
13+
creator: (ByteArray) -> I,
14+
from: ByteArray
15+
): T {
16+
return decode(creator(from))
17+
}
18+
19+
inline fun <I : ByteBuf, T> StreamDecoder<I, T>.decodeFromByteArray(
20+
from: ByteArray,
21+
creator: (ByteBuf) -> I
22+
) = decodeFromByteArray({ creator(Unpooled.wrappedBuffer(it)) }, from)
23+
24+
@Suppress("NOTHING_TO_INLINE")
25+
inline fun <T> StreamDecoder<SurfByteBuf, T>.decodeFromByteArray(from: ByteArray) =
26+
decodeFromByteArray(from) { SurfByteBuf(it) }
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,80 @@
11
package dev.slne.surf.cloud.api.common.netty.network.codec
22

3+
import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf
4+
import io.netty.buffer.ByteBuf
5+
import io.netty.buffer.Unpooled
6+
37
@FunctionalInterface
48
fun interface StreamEncoder<O, T> {
59
fun encode(buf: O, value: T)
10+
}
11+
12+
13+
inline fun <O, T> StreamEncoder<O, T>.encodeToByteArray(
14+
size: Int,
15+
creator: (ByteArray) -> O,
16+
value: T
17+
): ByteArray {
18+
val arr = ByteArray(size)
19+
val out = creator(arr)
20+
encode(out, value)
21+
return arr
22+
}
23+
24+
inline fun <O : ByteBuf, T> StreamEncoder<O, T>.encodeToByteArray(
25+
size: Int,
26+
creator: (ByteBuf) -> O,
27+
value: T
28+
): ByteArray {
29+
val arr = ByteArray(size)
30+
val buf = Unpooled.wrappedBuffer(arr)
31+
buf.clear()
32+
val out = creator(buf)
33+
encode(out, value)
34+
35+
val written = buf.writerIndex()
36+
return if (written == arr.size) arr else arr.copyOf(written)
37+
}
38+
39+
inline fun <O : ByteBuf, T> StreamEncoder<O, T>.encodeInto(
40+
target: ByteArray,
41+
creator: (ByteBuf) -> O,
42+
value: T
43+
): Int {
44+
val buf = Unpooled.wrappedBuffer(target)
45+
buf.clear()
46+
val out = creator(buf)
47+
encode(out, value)
48+
return buf.writerIndex()
49+
}
50+
51+
@Suppress("NOTHING_TO_INLINE")
52+
inline fun <T> StreamEncoder<SurfByteBuf, T>.encodeToByteArray(
53+
size: Int,
54+
value: T
55+
): ByteArray = encodeToByteArray(size, { buf: ByteBuf -> SurfByteBuf(buf) }, value)
56+
57+
@Suppress("NOTHING_TO_INLINE")
58+
inline fun <T> StreamEncoder<SurfByteBuf, T>.encodeInto(
59+
target: ByteArray,
60+
value: T
61+
): Int = encodeInto(target, { SurfByteBuf(it) }, value)
62+
63+
@Suppress("NOTHING_TO_INLINE")
64+
inline fun <T> StreamEncoder<SurfByteBuf, T>.encodeToByteArray(
65+
value: T,
66+
sizeOf: (T) -> Int
67+
): ByteArray = encodeToByteArray(sizeOf(value), value)
68+
69+
@Suppress("NOTHING_TO_INLINE")
70+
inline fun <T> StreamEncoder<SurfByteBuf, T>.encodeToByteArrayDynamic(
71+
value: T,
72+
initialCapacity: Int = 256
73+
): ByteArray {
74+
val buf = Unpooled.buffer(initialCapacity) // Heap-Buffer
75+
encode(SurfByteBuf(buf), value)
76+
val written = buf.writerIndex()
77+
val out = ByteArray(written)
78+
buf.getBytes(0, out)
79+
return out
680
}
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
package dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.adventure
22

3-
import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.CloudBufSerializer
4-
import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf
3+
import kotlinx.serialization.KSerializer
54
import kotlinx.serialization.Serializable
65
import kotlinx.serialization.descriptors.PrimitiveKind
76
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
7+
import kotlinx.serialization.encoding.Decoder
8+
import kotlinx.serialization.encoding.Encoder
89
import net.kyori.adventure.key.Key
910

1011
typealias SerializableKey = @Serializable(with = AdventureKeySerializer::class) Key
1112

12-
object AdventureKeySerializer : CloudBufSerializer<Key>() {
13+
object AdventureKeySerializer : KSerializer<Key> {
1314
override val descriptor = PrimitiveSerialDescriptor("Key", PrimitiveKind.STRING)
1415

15-
override fun serialize0(
16-
buf: SurfByteBuf,
17-
value: Key
18-
) {
19-
buf.writeKey(value)
16+
override fun serialize(encoder: Encoder, value: Key) {
17+
encoder.encodeString(value.asString())
2018
}
2119

22-
override fun deserialize0(buf: SurfByteBuf): Key {
23-
return buf.readKey()
20+
override fun deserialize(decoder: Decoder): Key {
21+
return Key.key(decoder.decodeString())
2422
}
2523
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package dev.slne.surf.cloud.api.common.player.cache
2+
3+
import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec
4+
import dev.slne.surf.cloud.api.common.netty.protocol.buffer.SurfByteBuf
5+
import kotlinx.serialization.Serializable
6+
import net.kyori.adventure.key.Key
7+
import java.util.concurrent.ConcurrentMap
8+
9+
@Serializable
10+
sealed interface CacheKey<T : Any> : Key {
11+
companion object {
12+
fun <T : Any> of(namespace: String, value: String): CacheKey<T> {
13+
return CacheKeyImpl(Key.key(namespace, value))
14+
}
15+
16+
fun <T : Any> of(key: Key): CacheKey<T> {
17+
return CacheKeyImpl(key)
18+
}
19+
20+
operator fun <T : Any> invoke(namespace: String, value: String): CacheKey<T> {
21+
return of(namespace, value)
22+
}
23+
24+
operator fun <T : Any> invoke(key: Key): CacheKey<T> {
25+
return of(key)
26+
}
27+
}
28+
29+
@Serializable
30+
sealed interface Value<T : Any> : CacheKey<T> {
31+
val codec: StreamCodec<SurfByteBuf, T>
32+
}
33+
34+
@Serializable
35+
sealed interface List<E : Any> : CacheKey<MutableList<E>> {
36+
val elementCodec: StreamCodec<SurfByteBuf, E>
37+
}
38+
39+
@Serializable
40+
sealed interface Set<E : Any> : CacheKey<MutableSet<E>> {
41+
val elementCodec: StreamCodec<SurfByteBuf, E>
42+
}
43+
44+
@Serializable
45+
sealed interface Map<K : Any, V : Any> : CacheKey<ConcurrentMap<K, V>> {
46+
val keyCodec: StreamCodec<SurfByteBuf, K>
47+
val valueCodec: StreamCodec<SurfByteBuf, V>
48+
}
49+
50+
@Serializable
51+
sealed interface Structured<T : Any, D: Any> : CacheKey<T>
52+
}
53+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.slne.surf.cloud.api.common.player.cache
2+
3+
import dev.slne.surf.cloud.api.common.netty.network.codec.kotlinx.adventure.SerializableKey
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class CacheKeyImpl<T : Any>(private val backing: SerializableKey) : CacheKey<T> {
8+
override fun namespace() = backing.namespace()
9+
override fun value() = backing.value()
10+
override fun asString() = backing.asString()
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package dev.slne.surf.cloud.api.common.player.cache
2+
3+
interface CacheStructured<T : Any, D : Any> : DirtyMarkable {
4+
var value: T
5+
val type: StructuredType<T, D>
6+
7+
fun emit(delta: D)
8+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dev.slne.surf.cloud.api.common.player.cache
2+
3+
import it.unimi.dsi.fastutil.objects.ObjectArrayList
4+
import it.unimi.dsi.fastutil.objects.ObjectList
5+
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
6+
import org.gradle.internal.impldep.it.unimi.dsi.fastutil.objects.Object2ObjectMap
7+
import java.util.concurrent.ConcurrentMap
8+
9+
interface DeltaBacked {
10+
var suppressOutbound: Boolean
11+
}
12+
13+
interface CacheList<E : Any> : ObjectList<E>, DeltaBacked {
14+
fun snapshot(): ObjectArrayList<E>
15+
}
16+
17+
interface CacheSet<E : Any> : MutableSet<E>, DeltaBacked {
18+
fun snapshot(): ObjectOpenHashSet<E>
19+
}
20+
21+
interface CacheMap<K : Any, V : Any> : ConcurrentMap<K, V>, DeltaBacked {
22+
fun snapshot(): Object2ObjectMap<K, V>
23+
24+
// TODO: 13.08.2025 18:38 - support these methods in a better way
25+
// override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
26+
// get() = throw UnsupportedOperationException("Use put/remove/clear on CacheMap")
27+
// override val keys: MutableSet<K>
28+
// get() = throw UnsupportedOperationException("Use put/remove/clear on CacheMap")
29+
// override val values: MutableCollection<V>
30+
// get() = throw UnsupportedOperationException("Use put/remove/clear on CacheMap")
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dev.slne.surf.cloud.api.common.player.cache
2+
3+
import org.jetbrains.annotations.ApiStatus
4+
import java.util.*
5+
6+
@ApiStatus.NonExtendable
7+
interface CloudPlayerCache {
8+
val uuid: UUID
9+
10+
operator fun <T : Any> set(key: CacheKey.Value<T>, value: T)
11+
operator fun <T : Any> get(key: CacheKey.Value<T>): T?
12+
fun remove(key: CacheKey<*>)
13+
14+
fun <E : Any> list(key: CacheKey.List<E>): CacheList<E>
15+
16+
fun <E : Any> set(
17+
key: CacheKey.Set<E>,
18+
): CacheSet<E>
19+
20+
fun <K : Any, V : Any> map(
21+
key: CacheKey.Map<K, V>,
22+
): CacheMap<K, V>
23+
24+
fun <T : Any, D : Any> structured(
25+
key: CacheKey.Structured<T, D>,
26+
type: StructuredType<T, D>,
27+
default: () -> T
28+
): CacheStructured<T, D>
29+
30+
fun <R> batch(block: CloudPlayerCacheBatch.() -> R): R
31+
32+
fun clear()
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dev.slne.surf.cloud.api.common.player.cache
2+
3+
interface CloudPlayerCacheBatch {
4+
fun <T : Any> set(key: CacheKey.Value<T>, value: T)
5+
fun remove(key: CacheKey<*>)
6+
7+
fun <E : Any> listAdd(
8+
key: CacheKey.List<E>,
9+
element: E,
10+
index: Int? = null
11+
)
12+
13+
fun <E : Any> listRemoveAt(
14+
key: CacheKey.List<E>,
15+
index: Int
16+
)
17+
18+
fun <E : Any> setAdd(
19+
key: CacheKey.Set<E>,
20+
element: E,
21+
)
22+
23+
fun <E : Any> setRemove(
24+
key: CacheKey.Set<E>,
25+
element: E,
26+
)
27+
28+
fun <K : Any, V : Any> mapPut(
29+
key: CacheKey.Map<K, V>,
30+
k: K,
31+
v: V,
32+
)
33+
34+
fun <K : Any, V : Any> mapRemove(
35+
key: CacheKey.Map<K, V>,
36+
k: K,
37+
)
38+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.slne.surf.cloud.api.common.player.cache
2+
3+
interface DirtyMarkable {
4+
fun markDirty()
5+
fun consumeDirty(): Boolean
6+
7+
}

0 commit comments

Comments
 (0)