From 7eddb363161c069e69115b6d6d2287a110d98d5e Mon Sep 17 00:00:00 2001 From: Ivan Pekov Date: Thu, 17 Mar 2022 21:18:59 +0200 Subject: [PATCH] new base --- .../squaremap/common/config/Config.java | 17 ++ .../common/config/DataStorageType.java | 5 + .../common/data/MapWorldInternal.java | 85 ++-------- .../data/storage/AdditionalParameters.java | 22 +++ .../common/data/storage/DataStorage.java | 31 ++++ .../data/storage/DataStorageHolder.java | 18 ++ .../data/storage/FlatfileDataStorage.java | 154 ++++++++++++++++++ 7 files changed, 257 insertions(+), 75 deletions(-) create mode 100644 common/src/main/java/xyz/jpenilla/squaremap/common/config/DataStorageType.java create mode 100644 common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/AdditionalParameters.java create mode 100644 common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorage.java create mode 100644 common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorageHolder.java create mode 100644 common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/FlatfileDataStorage.java diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java b/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java index 6aa0b197..e041f3d5 100644 --- a/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java @@ -2,8 +2,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.spongepowered.configurate.NodePath; import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import xyz.jpenilla.squaremap.common.Logging; @SuppressWarnings("unused") public final class Config extends AbstractConfig { @@ -114,6 +116,21 @@ private static void progressLogging() { PROGRESS_LOGGING_INTERVAL = config.getInt("settings.render-progress-logging.interval-seconds", 1); } + public static DataStorageType STORAGE_TYPE; + + private static void storage() { + try { + STORAGE_TYPE = DataStorageType.valueOf( + config.getString("settings.storage.type", "flatfile").toUpperCase(Locale.ROOT) + ); + } catch (IllegalArgumentException e) { + Logging.logger().error("Invalid storage type '" + + config.getString("settings.storage.type", "flatfile") + + "', falling back to flatfile."); + STORAGE_TYPE = DataStorageType.FLATFILE; + } + } + public static Config config() { return config; } diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/config/DataStorageType.java b/common/src/main/java/xyz/jpenilla/squaremap/common/config/DataStorageType.java new file mode 100644 index 00000000..7ac7439c --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/config/DataStorageType.java @@ -0,0 +1,5 @@ +package xyz.jpenilla.squaremap.common.config; + +public enum DataStorageType { + FLATFILE +} diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java index b3ff8552..89f73889 100644 --- a/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java @@ -1,19 +1,8 @@ package xyz.jpenilla.squaremap.common.data; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; -import java.io.BufferedReader; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -32,10 +21,10 @@ import xyz.jpenilla.squaremap.api.Registry; import xyz.jpenilla.squaremap.api.WorldIdentifier; import xyz.jpenilla.squaremap.common.LayerRegistry; -import xyz.jpenilla.squaremap.common.Logging; -import xyz.jpenilla.squaremap.common.SquaremapCommon; import xyz.jpenilla.squaremap.common.config.WorldAdvanced; import xyz.jpenilla.squaremap.common.config.WorldConfig; +import xyz.jpenilla.squaremap.common.data.storage.AdditionalParameters; +import xyz.jpenilla.squaremap.common.data.storage.DataStorageHolder; import xyz.jpenilla.squaremap.common.layer.SpawnIconProvider; import xyz.jpenilla.squaremap.common.layer.WorldBorderProvider; import xyz.jpenilla.squaremap.common.task.render.AbstractRender; @@ -43,22 +32,14 @@ import xyz.jpenilla.squaremap.common.task.render.FullRender; import xyz.jpenilla.squaremap.common.util.Colors; import xyz.jpenilla.squaremap.common.util.FileUtil; -import xyz.jpenilla.squaremap.common.util.RecordTypeAdapterFactory; import xyz.jpenilla.squaremap.common.util.Util; import xyz.jpenilla.squaremap.common.visibilitylimit.VisibilityLimitImpl; @DefaultQualifier(NonNull.class) public abstract class MapWorldInternal implements MapWorld { - private static final String DIRTY_CHUNKS_FILE_NAME = "dirty_chunks.json"; - private static final String RENDER_PROGRESS_FILE_NAME = "resume_render.json"; - private static final Gson GSON = new GsonBuilder() - .registerTypeAdapterFactory(new RecordTypeAdapterFactory()) - .enableComplexMapKeySerialization() - .create(); private static final Map LAYER_REGISTRIES = new HashMap<>(); private final ServerLevel level; - private final Path dataPath; private final Path tilesPath; private final ExecutorService imageIOexecutor; private final ScheduledExecutorService executor; @@ -90,17 +71,6 @@ protected MapWorldInternal(final ServerLevel level) { this.blockColors = new BlockColors(this); this.levelBiomeColorData = LevelBiomeColorData.create(this); - this.dataPath = SquaremapCommon.instance().platform().dataDirectory().resolve("data").resolve( - Util.levelWebName(this.level) - ); - try { - if (!Files.exists(this.dataPath)) { - Files.createDirectories(this.dataPath); - } - } catch (final IOException e) { - throw this.failedToCreateDataDirectory(e); - } - this.tilesPath = FileUtil.getAndCreateTilesDirectory(this.serverLevel()); this.startBackgroundRender(); @@ -124,53 +94,22 @@ protected MapWorldInternal(final ServerLevel level) { } public @Nullable Map getRenderProgress() { - try { - final Path file = this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME); - if (Files.isRegularFile(file)) { - final Type type = new TypeToken>() { - }.getType(); - try (final BufferedReader reader = Files.newBufferedReader(file)) { - return GSON.fromJson(reader, type); - } - } - } catch (JsonIOException | JsonSyntaxException | IOException e) { - Logging.logger().warn("Failed to deserialize render progress for world '{}'", this.identifier().asString(), e); - } - return null; + return DataStorageHolder.getDataStorage().getRenderProgress( + this.identifier(), + new AdditionalParameters().put("levelWebName", Util.levelWebName(this.level)) + ).join(); } public void saveRenderProgress(Map regions) { - try { - Files.writeString(this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME), GSON.toJson(regions)); - } catch (IOException e) { - Logging.logger().warn("Failed to serialize render progress for world '{}'", this.identifier().asString(), e); - } + DataStorageHolder.getDataStorage().storeRenderProgress(this.identifier(), regions, new AdditionalParameters().put("levelWebName", Util.levelWebName(this.level))); } private void serializeDirtyChunks() { - try { - Files.writeString(this.dataPath.resolve(DIRTY_CHUNKS_FILE_NAME), GSON.toJson(this.modifiedChunks)); - } catch (IOException e) { - Logging.logger().warn("Failed to serialize dirty chunks for world '{}'", this.identifier().asString(), e); - } + DataStorageHolder.getDataStorage().storeDirtyChunks(this.identifier(), this.modifiedChunks, new AdditionalParameters().put("levelWebName", Util.levelWebName(this.level))); } private void deserializeDirtyChunks() { - try { - final Path file = this.dataPath.resolve(DIRTY_CHUNKS_FILE_NAME); - if (Files.isRegularFile(file)) { - try (final BufferedReader reader = Files.newBufferedReader(file)) { - this.modifiedChunks.addAll( - GSON.fromJson( - reader, - TypeToken.getParameterized(List.class, ChunkCoordinate.class).getType() - ) - ); - } - } - } catch (JsonIOException | JsonSyntaxException | IOException e) { - Logging.logger().warn("Failed to deserialize dirty chunks for world '{}'", this.identifier().asString(), e); - } + this.modifiedChunks.addAll(DataStorageHolder.getDataStorage().getDirtyChunks(this.identifier(), new AdditionalParameters().put("levelWebName", Util.levelWebName(this.level))).join()); } private void startBackgroundRender() { @@ -255,11 +194,7 @@ public void pauseRenders(boolean pauseRenders) { } public void finishedRender() { - try { - Files.deleteIfExists(this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME)); - } catch (IOException e) { - Logging.logger().warn("Failed to delete render progress data for world '{}'", this.identifier().asString(), e); - } + DataStorageHolder.getDataStorage().deleteRenderProgress(this.identifier(), new AdditionalParameters().put("levelWebName", Util.levelWebName(this.level))); } public void stopRender() { diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/AdditionalParameters.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/AdditionalParameters.java new file mode 100644 index 00000000..f42a0786 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/AdditionalParameters.java @@ -0,0 +1,22 @@ +package xyz.jpenilla.squaremap.common.data.storage; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public final class AdditionalParameters { + + private final Map parameters = new ConcurrentHashMap<>(); + + public AdditionalParameters put(String key, Object value) { + this.parameters.put(key, value); + return this; + } + + public Optional get(String key) { + return Optional.ofNullable(parameters.get(key)); + } +} diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorage.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorage.java new file mode 100644 index 00000000..ef410760 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorage.java @@ -0,0 +1,31 @@ +package xyz.jpenilla.squaremap.common.data.storage; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import xyz.jpenilla.squaremap.api.WorldIdentifier; +import xyz.jpenilla.squaremap.common.data.ChunkCoordinate; +import xyz.jpenilla.squaremap.common.data.RegionCoordinate; + +@DefaultQualifier(NonNull.class) +public interface DataStorage { + + void storeDirtyChunks(WorldIdentifier world, Set dirtyChunkCoordinates, AdditionalParameters parameters); + + CompletableFuture> getDirtyChunks(WorldIdentifier world, AdditionalParameters parameters); + + void storeRenderProgress(WorldIdentifier world, Map renderProgress, AdditionalParameters parameters); + + CompletableFuture> getRenderProgress(WorldIdentifier world, AdditionalParameters parameters); + + void deleteRenderProgress(WorldIdentifier world, AdditionalParameters parameters); + + void updateMarkers(WorldIdentifier world, List> layers, AdditionalParameters parameters); + + void updateWorldSettings(WorldIdentifier world, Map settings, AdditionalParameters parameters); + + void updateGlobalSettings(WorldIdentifier world, Map settings, AdditionalParameters parameters); +} diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorageHolder.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorageHolder.java new file mode 100644 index 00000000..fef2f69a --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/DataStorageHolder.java @@ -0,0 +1,18 @@ +package xyz.jpenilla.squaremap.common.data.storage; + +import xyz.jpenilla.squaremap.common.config.Config; + +public class DataStorageHolder { + + private static DataStorage dataStorage; + + public static DataStorage getDataStorage() { + if (dataStorage == null) { + switch (Config.STORAGE_TYPE) { + case FLATFILE -> dataStorage = new FlatfileDataStorage(); + } + } + return dataStorage; + } + +} diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/FlatfileDataStorage.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/FlatfileDataStorage.java new file mode 100644 index 00000000..29f21152 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/storage/FlatfileDataStorage.java @@ -0,0 +1,154 @@ +package xyz.jpenilla.squaremap.common.data.storage; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import xyz.jpenilla.squaremap.api.WorldIdentifier; +import xyz.jpenilla.squaremap.common.Logging; +import xyz.jpenilla.squaremap.common.SquaremapCommon; +import xyz.jpenilla.squaremap.common.data.ChunkCoordinate; +import xyz.jpenilla.squaremap.common.data.RegionCoordinate; +import xyz.jpenilla.squaremap.common.util.RecordTypeAdapterFactory; + +@DefaultQualifier(NonNull.class) +public class FlatfileDataStorage implements DataStorage { + private static final String DIRTY_CHUNKS_FILE_NAME = "dirty_chunks.json"; + private static final String RENDER_PROGRESS_FILE_NAME = "resume_render.json"; + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapterFactory(new RecordTypeAdapterFactory()) + .enableComplexMapKeySerialization() + .create(); + + private final Map dataPaths = new ConcurrentHashMap<>(); + + private Path getDataPath(WorldIdentifier identifier, String worldLevelWebName) { + return this.dataPaths.computeIfAbsent(worldLevelWebName, k -> { + Path path = SquaremapCommon.instance().platform().dataDirectory().resolve("data").resolve( + worldLevelWebName + ); + try { + if (!Files.exists(path)) { + Files.createDirectories(path); + } + } catch (IOException e) { + throw new IllegalStateException(String.format("Failed to create data directory for world '%s'", identifier), e); + } + return path; + }); + } + + @Override + public void storeDirtyChunks(WorldIdentifier world, Set dirtyChunkCoordinates, AdditionalParameters parameters) { + Path dataPath = getDataPath( + world, + (String) parameters.get("levelWebName").orElseThrow(() -> new IllegalArgumentException("Couldn't get levelWebName")) + ); + try { + Files.writeString(dataPath.resolve(DIRTY_CHUNKS_FILE_NAME), GSON.toJson(dirtyChunkCoordinates)); + } catch (IOException e) { + Logging.logger().warn("Failed to serialize dirty chunks for world '{}'", world.asString(), e); + } + } + + @Override + public CompletableFuture> getDirtyChunks(WorldIdentifier world, AdditionalParameters parameters) { + Path dataPath = getDataPath( + world, + (String) parameters.get("levelWebName").orElseThrow(() -> new IllegalArgumentException("Couldn't get levelWebName")) + ); + Set ret = ConcurrentHashMap.newKeySet(); + try { + final Path file = dataPath.resolve(DIRTY_CHUNKS_FILE_NAME); + if (Files.isRegularFile(file)) { + try (final BufferedReader reader = Files.newBufferedReader(file)) { + ret.addAll( + GSON.fromJson( + reader, + TypeToken.getParameterized(List.class, ChunkCoordinate.class).getType() + ) + ); + } + } + } catch (JsonIOException | JsonSyntaxException | IOException e) { + Logging.logger().warn("Failed to deserialize dirty chunks for world '{}'", world.asString(), e); + } + return CompletableFuture.completedFuture(ret); + } + + @Override + public void storeRenderProgress(WorldIdentifier world, Map renderProgress, AdditionalParameters parameters) { + Path dataPath = getDataPath( + world, + (String) parameters.get("levelWebName").orElseThrow(() -> new IllegalArgumentException("Couldn't get levelWebName")) + ); + try { + Files.writeString(dataPath.resolve(RENDER_PROGRESS_FILE_NAME), GSON.toJson(renderProgress)); + } catch (IOException e) { + Logging.logger().warn("Failed to serialize render progress for world '{}'", world.asString(), e); + } + } + + @Override + public CompletableFuture> getRenderProgress(WorldIdentifier world, AdditionalParameters parameters) { + Path dataPath = getDataPath( + world, + (String) parameters.get("levelWebName").orElseThrow(() -> new IllegalArgumentException("Couldn't get levelWebName")) + ); + try { + final Path file = dataPath.resolve(RENDER_PROGRESS_FILE_NAME); + if (Files.isRegularFile(file)) { + final Type type = new TypeToken>() { + }.getType(); + try (final BufferedReader reader = Files.newBufferedReader(file)) { + return CompletableFuture.completedFuture(GSON.fromJson(reader, type)); + } + } + } catch (JsonIOException | JsonSyntaxException | IOException e) { + Logging.logger().warn("Failed to deserialize render progress for world '{}'", world.asString(), e); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public void deleteRenderProgress(WorldIdentifier world, AdditionalParameters parameters) { + Path dataPath = getDataPath( + world, + (String) parameters.get("levelWebName").orElseThrow(() -> new IllegalArgumentException("Couldn't get levelWebName")) + ); + try { + Files.deleteIfExists(dataPath.resolve(RENDER_PROGRESS_FILE_NAME)); + } catch (IOException e) { + Logging.logger().warn("Failed to delete render progress data for world '{}'", world.asString(), e); + } + } + + @Override + public void updateMarkers(WorldIdentifier world, List> layers, AdditionalParameters parameters) { + // todo + } + + @Override + public void updateWorldSettings(WorldIdentifier world, Map settings, AdditionalParameters parameters) { + // todo + } + + @Override + public void updateGlobalSettings(WorldIdentifier world, Map settings, AdditionalParameters parameters) { + // todo + } +}