Started work on new persistent data

This commit is contained in:
Auxilor
2024-08-21 18:07:09 +01:00
parent 665857a00f
commit f1b71c2ac9
6 changed files with 302 additions and 5 deletions

View File

@@ -0,0 +1,67 @@
package com.willfp.eco.core.data.handlers;
import com.willfp.eco.core.data.keys.PersistentDataKey;
import com.willfp.eco.core.registry.Registrable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.UUID;
public abstract class PersistentDataHandler implements Registrable {
/**
* The id of the handler.
*/
private final String id;
/**
* Create a new persistent data handler.
*
* @param id The id of the handler.
*/
protected PersistentDataHandler(@NotNull final String id) {
this.id = id;
}
@Override
public @NotNull String getID() {
return id;
}
/**
* Read a key from persistent data.
*
* @param uuid The uuid.
* @param key The key.
* @param <T> The type of the key.
* @return The value, or null if not found.
*/
@Nullable
public abstract <T> T read(@NotNull UUID uuid, @NotNull PersistentDataKey<T> key);
/**
* Write a key to persistent data.
*
* @param uuid The uuid.
* @param key The key.
* @param value The value.
* @param <T> The type of the key.
*/
public abstract <T> void write(@NotNull UUID uuid, @NotNull PersistentDataKey<T> key, @NotNull T value);
/**
* Serialize data.
*
* @param keys The keys to serialize.
* @return The serialized data.
*/
@NotNull
public abstract Set<SerializedProfile> serializeData(@NotNull final Set<PersistentDataKey<?>> keys);
/**
* Load profile data.
*
* @param data The data.
*/
public abstract void loadProfileData(@NotNull Set<SerializedProfile> data);
}

View File

@@ -0,0 +1,42 @@
package com.willfp.eco.core.data.handlers;
import com.willfp.eco.core.registry.Registry;
import org.jetbrains.annotations.NotNull;
/**
* Utility class to manage persistent data handlers.
*/
public final class PersistentDataHandlers {
private static final Registry<PersistentDataHandler> REGISTRY = new Registry<>();
/**
* Register a persistent data handler.
*
* @param handler The handler.
*/
public static void register(@NotNull final PersistentDataHandler handler) {
REGISTRY.register(handler);
}
/**
* Get a persistent data handler by id.
*
* @param id The id.
* @return The handler.
* @throws IllegalArgumentException if no handler with that id is found.
*/
@NotNull
public static PersistentDataHandler get(@NotNull final String id) {
PersistentDataHandler handler = REGISTRY.get(id);
if (handler == null) {
throw new IllegalArgumentException("No handler with id: " + id);
}
return handler;
}
private PersistentDataHandlers() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
}

View File

@@ -0,0 +1,20 @@
package com.willfp.eco.core.data.handlers;
import com.willfp.eco.core.data.keys.PersistentDataKey;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.UUID;
/**
* Serialized profile.
*
* @param uuid The uuid.
* @param data The data.
*/
public record SerializedProfile(
@NotNull UUID uuid,
@NotNull Map<PersistentDataKey<?>, Object> data
) {
}

View File

@@ -0,0 +1,108 @@
package com.willfp.eco.internal.spigot.data.handlers
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.data.handlers.PersistentDataHandler
import com.willfp.eco.core.data.handlers.SerializedProfile
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import java.util.UUID
import java.util.concurrent.Executors
import com.mongodb.kotlin.client.coroutine.MongoClient
import com.willfp.eco.core.config.Configs
import com.willfp.eco.internal.spigot.data.storage.UUIDProfile
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.forEach
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.bson.Document
class MongoPersistentDataHandler(
config: Config,
plugin: EcoSpigotPlugin
) : PersistentDataHandler("yaml") {
private val url: String = config.getString("url") ?: error("MongoDB URL not found in config")
private val databaseName: String = config.getString("database") ?: error("Database name not found in config")
private val client = MongoClient.create(url)
private val database = client.getDatabase(databaseName)
private val collection = database.getCollection<UUIDProfile>("uuidprofile")
private val executor = Executors.newCachedThreadPool()
override fun <T: Any> read(uuid: UUID, key: PersistentDataKey<T>): T? {
return runBlocking {
doRead(uuid, key)
}
}
private suspend fun <T: Any> doRead(uuid: UUID, key: PersistentDataKey<T>): T? {
val document = collection.find(Document("uuid", uuid.toString())).firstOrNull() ?: return null
val data = document.data[key.key.toString()] as? T
return data
}
override fun <T : Any> write(uuid: UUID, key: PersistentDataKey<T>, value: T) {
executor.submit {
runBlocking {
doWrite(uuid, key, value)
}
}
}
private suspend fun <T : Any> doWrite(uuid: UUID, key: PersistentDataKey<T>, value: T) {
val document = collection.find(Document("uuid", uuid.toString())).firstOrNull() ?: return null
document.data[key.key.toString()] = value
collection.replaceOne(Document("uuid", uuid.toString()), document)
}
override fun serializeData(keys: Set<PersistentDataKey<*>>): Set<SerializedProfile> {
val profiles = mutableSetOf<SerializedProfile>()
collection.find().forEach { document ->
val uuid = UUID.fromString(document.getString("uuid"))
val data = document.get("data") as Document
val profileData = keys.associateWith { key ->
when (key.type) {
PersistentDataKeyType.STRING -> data.getString(key.key.key)
PersistentDataKeyType.BOOLEAN -> data.getBoolean(key.key.key)
PersistentDataKeyType.INT -> data.getInteger(key.key.key)
PersistentDataKeyType.DOUBLE -> data.getDouble(key.key.key)
PersistentDataKeyType.STRING_LIST -> data.getList(key.key.key, String::class.java)
PersistentDataKeyType.BIG_DECIMAL -> data.getDecimal128(key.key.key)?.bigDecimalValue()
PersistentDataKeyType.CONFIG -> data.get(key.key.key)
else -> null
} ?: key.defaultValue
}
profiles.add(SerializedProfile(uuid, profileData as Map<PersistentDataKey<*>, Any>))
}
return profiles
}
override fun loadProfileData(data: Set<SerializedProfile>) {
data.forEach { profile ->
val document = Document("uuid", profile.uuid.toString())
val profileData = Document()
profile.data.forEach { (key, value) ->
profileData.put(key.key.key, value)
}
document.put("data", profileData)
collection.replaceOne(Document("uuid", profile.uuid.toString()), document, com.mongodb.client.model.ReplaceOptions().upsert(true))
}
}
@Serializable
internal data class UUIDProfile(
// Storing UUID as strings for serialization
@SerialName("_id") val uuid: String,
// Storing NamespacedKeys as strings for serialization
val data: MutableMap<String, @Contextual Any>
)
}

View File

@@ -0,0 +1,63 @@
package com.willfp.eco.internal.spigot.data.handlers
import com.willfp.eco.core.data.handlers.PersistentDataHandler
import com.willfp.eco.core.data.handlers.SerializedProfile
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import java.util.UUID
class YamlPersistentDataHandler(
plugin: EcoSpigotPlugin
) : PersistentDataHandler("yaml") {
private val dataYml = plugin.dataYml
@Suppress("UNCHECKED_CAST")
override fun <T : Any> read(uuid: UUID, key: PersistentDataKey<T>): T? {
// Separate `as T?` for each branch to prevent compiler warnings.
val value = when (key.type) {
PersistentDataKeyType.INT -> dataYml.getIntOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.DOUBLE -> dataYml.getDoubleOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.STRING -> dataYml.getStringOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.BOOLEAN -> dataYml.getBoolOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.STRING_LIST -> dataYml.getStringsOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.CONFIG -> dataYml.getSubsectionOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.BIG_DECIMAL -> dataYml.getBigDecimalOrNull("player.$uuid.${key.key}") as T?
else -> null
}
return value
}
override fun <T : Any> write(uuid: UUID, key: PersistentDataKey<T>, value: T) {
dataYml.set("player.$uuid.$key", value)
}
override fun serializeData(keys: Set<PersistentDataKey<*>>): Set<SerializedProfile> {
val profiles = mutableSetOf<SerializedProfile>()
val uuids = dataYml.getSubsection("player").getKeys(false).map { UUID.fromString(it) }
for (uuid in uuids) {
val data = mutableMapOf<PersistentDataKey<*>, Any>()
for (key in keys) {
data[key] = read(uuid, key) ?: continue
}
profiles.add(SerializedProfile(uuid, data))
}
return profiles
}
override fun loadProfileData(data: Set<SerializedProfile>) {
for (profile in data) {
for ((key, value) in profile.data) {
// Dirty cast, but it's fine because we know it's the same type
@Suppress("UNCHECKED_CAST")
write(profile.uuid, key as PersistentDataKey<Any>, value as Any)
}
}
}
}

View File

@@ -2,7 +2,6 @@ package com.willfp.eco.internal.spigot.data.storage
import com.mongodb.client.model.Filters
import com.mongodb.client.model.ReplaceOptions
import com.mongodb.client.model.UpdateOptions
import com.mongodb.client.model.Updates
import com.mongodb.kotlin.client.coroutine.MongoClient
import com.mongodb.kotlin.client.coroutine.MongoCollection
@@ -11,15 +10,13 @@ import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.data.ProfileHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.bson.codecs.pojo.annotations.BsonId
import java.util.UUID
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.bukkit.Bukkit
import java.util.UUID
@Suppress("UNCHECKED_CAST")
class MongoDataHandler(