Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrote persistent data system #375

Merged
merged 11 commits into from
Aug 25, 2024
Merged
3 changes: 0 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ plugins {
id("maven-publish")
id("java")
kotlin("jvm") version "1.9.21"
kotlin("plugin.serialization") version "1.9.21"
}

dependencies {
Expand All @@ -41,7 +40,6 @@ allprojects {
apply(plugin = "maven-publish")
apply(plugin = "io.github.goooler.shadow")
apply(plugin = "kotlin")
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")

repositories {
mavenCentral()
Expand Down Expand Up @@ -212,7 +210,6 @@ tasks {
//relocate("com.mysql", "com.willfp.eco.libs.mysql")
relocate("com.mongodb", "com.willfp.eco.libs.mongodb")
relocate("org.bson", "com.willfp.eco.libs.bson")
relocate("org.litote", "com.willfp.eco.libs.litote")
relocate("org.reactivestreams", "com.willfp.eco.libs.reactivestreams")
relocate("reactor.", "com.willfp.eco.libs.reactor.") // Dot in name to be safe
relocate("com.moandjiezana.toml", "com.willfp.eco.libs.toml")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.willfp.eco.core.data.handlers;

import com.willfp.eco.core.data.keys.PersistentDataKey;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

/**
* Handles data read/write for a {@link com.willfp.eco.core.data.keys.PersistentDataKeyType} for a specific
* data handler.
*
* @param <T> The type of data.
*/
public abstract class DataTypeSerializer<T> {
/**
* Create a new data type serializer.
*/
protected DataTypeSerializer() {

}

/**
* Read a value.
*
* @param uuid The uuid.
* @param key The key.
* @return The value.
*/
@Nullable
public abstract T readAsync(@NotNull final UUID uuid,
@NotNull final PersistentDataKey<T> key);

/**
* Write a value.
*
* @param uuid The uuid.
* @param key The key.
* @param value The value.
*/
public abstract void writeAsync(@NotNull final UUID uuid,
@NotNull final PersistentDataKey<T> key,
@NotNull final T value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package com.willfp.eco.core.data.handlers;

import com.willfp.eco.core.Eco;
import com.willfp.eco.core.data.keys.PersistentDataKey;
import com.willfp.eco.core.registry.Registrable;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
* Handles persistent data.
*/
public abstract class PersistentDataHandler implements Registrable {
/**
* The id.
*/
private final String id;

/**
* The executor.
*/
private final ExecutorService executor = Executors.newCachedThreadPool();

/**
* Create a new persistent data handler.
*
* @param id The id.
*/
protected PersistentDataHandler(@NotNull final String id) {
this.id = id;
}

/**
* Get all UUIDs with saved data.
*
* @return All saved UUIDs.
*/
protected abstract Set<UUID> getSavedUUIDs();

/**
* Save to disk.
* <p>
* If write commits to disk, this method does not need to be overridden.
* <p>
* This method is called asynchronously.
*/
protected void doSave() {
// Save to disk
}

/**
* If the handler should autosave.
*
* @return If the handler should autosave.
*/
public boolean shouldAutosave() {
return true;
}

/**
* Save the data.
*/
public final void save() {
executor.submit(this::doSave);
}

/**
* 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 final <T> T read(@NotNull final UUID uuid,
@NotNull final PersistentDataKey<T> key) {
DataTypeSerializer<T> serializer = key.getType().getSerializer(this);
Future<T> future = executor.submit(() -> serializer.readAsync(uuid, key));

try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return null;
}
}

/**
* 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 final <T> void write(@NotNull final UUID uuid,
@NotNull final PersistentDataKey<T> key,
@NotNull final T value) {
DataTypeSerializer<T> serializer = key.getType().getSerializer(this);
executor.submit(() -> serializer.writeAsync(uuid, key, value));
}

/**
* Serialize data.
*
* @param keys The keys to serialize.
* @return The serialized data.
*/
@NotNull
public final Set<SerializedProfile> serializeData(@NotNull final Set<PersistentDataKey<?>> keys) {
Set<SerializedProfile> profiles = new HashSet<>();

for (UUID uuid : getSavedUUIDs()) {
Map<PersistentDataKey<?>, Object> data = new HashMap<>();

for (PersistentDataKey<?> key : keys) {
Object value = read(uuid, key);

if (value != null) {
data.put(key, value);
}
}

profiles.add(new SerializedProfile(uuid, data));
}

return profiles;
}

/**
* Load profile data.
*
* @param profile The profile.
*/
@SuppressWarnings("unchecked")
public final void loadSerializedProfile(@NotNull final SerializedProfile profile) {
for (Map.Entry<PersistentDataKey<?>, Object> entry : profile.data().entrySet()) {
PersistentDataKey<?> key = entry.getKey();
Object value = entry.getValue();

// This cast is safe because the data is serialized
write(profile.uuid(), (PersistentDataKey<? super Object>) key, value);
}
}

/**
* Save and shutdown the handler.
*
* @throws InterruptedException If the writes could not be awaited.
*/
public final void shutdown() throws InterruptedException {
doSave();

if (executor.isShutdown()) {
return;
}

executor.shutdown();
while (!executor.awaitTermination(2, TimeUnit.MINUTES)) {
// Wait
}
}

@Override
@NotNull
public final String getID() {
return id;
}
}
Original file line number Diff line number Diff line change
@@ -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
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,11 @@ public final class PersistentDataKey<T> {
* @param key The key.
* @param type The data type.
* @param defaultValue The default value.
* @param isLocal If the key uses local storage.
*/
public PersistentDataKey(@NotNull final NamespacedKey key,
@NotNull final PersistentDataKeyType<T> type,
@NotNull final T defaultValue,
final boolean isLocal) {
this.key = key;
this.defaultValue = defaultValue;
this.type = type;
this.isLocal = isLocal;

Eco.get().registerPersistentKey(this);
@NotNull final T defaultValue) {
this(key, type, defaultValue, false);
}

/**
Expand All @@ -60,14 +53,16 @@ public PersistentDataKey(@NotNull final NamespacedKey key,
* @param key The key.
* @param type The data type.
* @param defaultValue The default value.
* @param isLocal If the key uses local storage.
*/
public PersistentDataKey(@NotNull final NamespacedKey key,
@NotNull final PersistentDataKeyType<T> type,
@NotNull final T defaultValue) {
@NotNull final T defaultValue,
final boolean isLocal) {
this.key = key;
this.defaultValue = defaultValue;
this.type = type;
this.isLocal = false;
this.isLocal = isLocal;

Eco.get().registerPersistentKey(this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.willfp.eco.core.data.keys;

import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.data.handlers.DataTypeSerializer;
import com.willfp.eco.core.data.handlers.PersistentDataHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
Expand Down Expand Up @@ -60,24 +64,58 @@ public final class PersistentDataKeyType<T> {
*/
private final String name;

/**
* The serializers for this key type.
*/
private final Map<PersistentDataHandler, DataTypeSerializer<T>> serializers = new HashMap<>();

/**
* Create new PersistentDataKeyType.
*
* @param name The name.
*/
private PersistentDataKeyType(@NotNull final String name) {
VALUES.add(this);

this.name = name;
}

/**
* Get the name of the key type.
*
* @return The name.
*/
@NotNull
public String name() {
return name;
}

/**
* Create new PersistentDataKeyType.
* Register a serializer for this key type.
*
* @param name The name.
* @param handler The handler.
* @param serializer The serializer.
*/
private PersistentDataKeyType(@NotNull final String name) {
VALUES.add(this);
public void registerSerializer(@NotNull final PersistentDataHandler handler,
@NotNull final DataTypeSerializer<T> serializer) {
this.serializers.put(handler, serializer);
}

this.name = name;
/**
* Get the serializer for a handler.
*
* @param handler The handler.
* @return The serializer.
*/
@NotNull
public DataTypeSerializer<T> getSerializer(@NotNull final PersistentDataHandler handler) {
DataTypeSerializer<T> serializer = this.serializers.get(handler);

if (serializer == null) {
throw new IllegalArgumentException("No serializer for handler: " + handler);
}

return serializer;
}

@Override
Expand Down
Loading
Loading