diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapConfiguration.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapConfiguration.java index 51406385..c51af95a 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapConfiguration.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapConfiguration.java @@ -26,7 +26,6 @@ import de.bluecolored.bluemap.common.config.*; import de.bluecolored.bluemap.common.config.storage.StorageConfig; -import de.bluecolored.bluemap.core.MinecraftVersion; import org.jetbrains.annotations.Nullable; import java.nio.file.Path; @@ -34,7 +33,7 @@ public interface BlueMapConfiguration { - MinecraftVersion getMinecraftVersion(); + @Nullable String getMinecraftVersion(); CoreConfig getCoreConfig(); @@ -48,7 +47,7 @@ public interface BlueMapConfiguration { Map getStorageConfigs(); - @Nullable Path getResourcePacksFolder(); + @Nullable Path getPacksFolder(); @Nullable Path getModsFolder(); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java index 9a05c83d..0ab90b72 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java @@ -36,12 +36,13 @@ import de.bluecolored.bluemap.common.config.MapConfig; import de.bluecolored.bluemap.common.config.storage.StorageConfig; import de.bluecolored.bluemap.common.plugin.Plugin; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.debug.StateDumper; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; -import de.bluecolored.bluemap.core.resources.datapack.DataPack; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.MinecraftVersion; +import de.bluecolored.bluemap.core.resources.VersionManifest; +import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.util.FileHelper; import de.bluecolored.bluemap.core.util.Key; @@ -74,6 +75,7 @@ public class BlueMapService implements Closeable { private final BlueMapConfiguration config; private final WebFilesManager webFilesManager; + private MinecraftVersion minecraftVersion; private ResourcePack resourcePack; private final Map worlds; private final Map maps; @@ -225,7 +227,7 @@ private synchronized void loadMap(String id, MapConfig mapConfig) throws Configu if (world == null) { try { Logger.global.logDebug("Loading world " + worldId + " ..."); - world = MCAWorld.load(worldFolder, dimension); + world = MCAWorld.load(worldFolder, dimension, loadDataPack(worldFolder)); worlds.put(worldId, world); } catch (IOException ex) { throw new ConfigurationException( @@ -320,108 +322,17 @@ public synchronized Storage getOrLoadStorage(String storageId) throws Configurat public synchronized ResourcePack getOrLoadResourcePack() throws ConfigurationException, InterruptedException { if (resourcePack == null) { - MinecraftVersion minecraftVersion = config.getMinecraftVersion(); - @Nullable Path resourcePackFolder = config.getResourcePacksFolder(); - @Nullable Path modsFolder = config.getModsFolder(); - - Path defaultResourceFile = config.getCoreConfig().getData().resolve("minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar"); - Path resourceExtensionsFile = config.getCoreConfig().getData().resolve("resourceExtensions.zip"); - - try { - FileHelper.createDirectories(resourcePackFolder); - } catch (IOException ex) { - throw new ConfigurationException( - "BlueMap failed to create this folder:\n" + - resourcePackFolder + "\n" + - "Does BlueMap have sufficient permissions?", - ex); - } + MinecraftVersion minecraftVersion = getOrLoadMinecraftVersion(); + Path vanillaResourcePack = minecraftVersion.getResourcePack(); if (Thread.interrupted()) throw new InterruptedException(); - if (!Files.exists(defaultResourceFile)) { - if (config.getCoreConfig().isAcceptDownload()) { - //download file - try { - Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ..."); - - FileHelper.createDirectories(defaultResourceFile.getParent()); - Path tempResourceFile = defaultResourceFile.getParent().resolve(defaultResourceFile.getFileName() + ".filepart"); - Files.deleteIfExists(tempResourceFile); - FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), tempResourceFile.toFile(), 10000, 10000); - FileHelper.move(tempResourceFile, defaultResourceFile); - } catch (IOException ex) { - throw new ConfigurationException("Failed to download resources!", ex); - } - - } else { - throw new MissingResourcesException(); - } - } - - if (Thread.interrupted()) throw new InterruptedException(); + Deque packRoots = getPackRoots(); + packRoots.addLast(vanillaResourcePack); try { - Files.deleteIfExists(resourceExtensionsFile); - FileHelper.createDirectories(resourceExtensionsFile.getParent()); - URL resourceExtensionsUrl = Objects.requireNonNull( - Plugin.class.getResource( - "/de/bluecolored/bluemap/" + minecraftVersion.getResource().getResourcePrefix() + - "/resourceExtensions.zip") - ); - FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile.toFile(), 10000, 10000); - } catch (IOException ex) { - throw new ConfigurationException( - "Failed to create resourceExtensions.zip!\n" + - "Does BlueMap has sufficient write permissions?", - ex); - } - - if (Thread.interrupted()) throw new InterruptedException(); - - try { - ResourcePack resourcePack = new ResourcePack(); - - List resourcePackRoots = new ArrayList<>(); - - if (resourcePackFolder != null) { - // load from resourcepack folder - try (Stream resourcepackFiles = Files.list(resourcePackFolder)) { - resourcepackFiles - .sorted(Comparator.reverseOrder()) - .forEach(resourcePackRoots::add); - } - } - - if (config.getCoreConfig().isScanForModResources()) { - - // load from mods folder - if (modsFolder != null && Files.isDirectory(modsFolder)) { - try (Stream resourcepackFiles = Files.list(modsFolder)) { - resourcepackFiles - .filter(Files::isRegularFile) - .filter(file -> file.getFileName().toString().endsWith(".jar")) - .forEach(resourcePackRoots::add); - } - } - - // load from datapacks - for (Path worldFolder : getWorldFolders()) { - Path datapacksFolder = worldFolder.resolve("datapacks"); - if (!Files.isDirectory(datapacksFolder)) continue; - - try (Stream resourcepackFiles = Files.list(worldFolder.resolve("datapacks"))) { - resourcepackFiles.forEach(resourcePackRoots::add); - } - } - - } - - resourcePackRoots.add(resourceExtensionsFile); - resourcePackRoots.add(defaultResourceFile); - - resourcePack.loadResources(resourcePackRoots); - + ResourcePack resourcePack = new ResourcePack(minecraftVersion.getResourcePackVersion()); + resourcePack.loadResources(packRoots); this.resourcePack = resourcePack; } catch (IOException | RuntimeException e) { throw new ConfigurationException("Failed to parse resources!\n" + @@ -432,17 +343,125 @@ public synchronized ResourcePack getOrLoadResourcePack() throws ConfigurationExc return this.resourcePack; } - private Collection getWorldFolders() { - Set folders = new HashSet<>(); - for (MapConfig mapConfig : config.getMapConfigs().values()) { - Path folder = mapConfig.getWorld(); - if (folder == null) continue; - folder = folder.toAbsolutePath().normalize(); - if (Files.isDirectory(folder)) { - folders.add(folder); + public synchronized DataPack loadDataPack(Path worldFolder) throws ConfigurationException, InterruptedException { + MinecraftVersion minecraftVersion = getOrLoadMinecraftVersion(); + Path vanillaDataPack = minecraftVersion.getDataPack(); + + if (Thread.interrupted()) throw new InterruptedException(); + + // also load world datapacks + Iterable worldPacks = List.of(); + Path worldPacksFolder = worldFolder.resolve("datapacks"); + if (Files.isDirectory(worldPacksFolder)) { + try (Stream worldPacksStream = Files.list(worldPacksFolder)) { + worldPacks = worldPacksStream.toList(); + } catch (IOException e) { + throw new ConfigurationException("Failed to access the worlds datapacks folder.", e); } } - return folders; + + Deque packRoots = getPackRoots(worldPacks); + packRoots.addLast(vanillaDataPack); + + try { + DataPack datapack = new DataPack(minecraftVersion.getDataPackVersion()); + datapack.loadResources(packRoots); + return datapack; + } catch (IOException | RuntimeException e) { + throw new ConfigurationException("Failed to parse resources!\n" + + "Is one of your resource-packs corrupted?", e); + } + } + + private synchronized Deque getPackRoots(Path... additionalRoots) throws ConfigurationException, InterruptedException { + return getPackRoots(List.of(additionalRoots)); + } + + private synchronized Deque getPackRoots(Iterable additionalRoots) throws ConfigurationException, InterruptedException { + @Nullable Path packsFolder = config.getPacksFolder(); + @Nullable Path modsFolder = config.getModsFolder(); + + try { + FileHelper.createDirectories(packsFolder); + } catch (IOException ex) { + throw new ConfigurationException( + "BlueMap failed to create this folder:\n" + + packsFolder + "\n" + + "Does BlueMap have sufficient permissions?", + ex); + } + + Path resourceExtensionsFile = config.getCoreConfig().getData().resolve("resourceExtensions.zip"); + + if (Thread.interrupted()) throw new InterruptedException(); + + try { + Files.deleteIfExists(resourceExtensionsFile); + FileHelper.createDirectories(resourceExtensionsFile.getParent()); + URL resourceExtensionsUrl = Objects.requireNonNull( + Plugin.class.getResource("/de/bluecolored/bluemap/resourceExtensions.zip") + ); + FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile.toFile(), 10000, 10000); + } catch (IOException ex) { + throw new ConfigurationException( + "Failed to create resourceExtensions.zip!\n" + + "Does BlueMap has sufficient write permissions?", + ex); + } + + Deque packRoots = new LinkedList<>(); + + // load from pack folder + if (packsFolder != null && Files.isDirectory(packsFolder)) { + try (Stream packFiles = Files.list(packsFolder)) { + packFiles + .sorted(Comparator.reverseOrder()) + .forEach(packRoots::add); + } catch (IOException e) { + throw new ConfigurationException("Failed to access packs folder.", e); + } + } + + // add additional roots + additionalRoots.forEach(packRoots::add); + + // load from mods folder + if (config.getCoreConfig().isScanForModResources() && modsFolder != null && Files.isDirectory(modsFolder)) { + try (Stream packFiles = Files.list(modsFolder)) { + packFiles + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".jar")) + .forEach(packRoots::add); + } catch (IOException e) { + throw new ConfigurationException("Failed to access packs folder.", e); + } + } + + packRoots.add(resourceExtensionsFile); + return packRoots; + } + + public synchronized MinecraftVersion getOrLoadMinecraftVersion() throws ConfigurationException { + if (this.minecraftVersion == null) { + try { + this.minecraftVersion = MinecraftVersion.load( + config.getMinecraftVersion(), + config.getCoreConfig().getData(), + config.getCoreConfig().isAcceptDownload() + ); + } catch (IOException ex) { + if (!config.getCoreConfig().isAcceptDownload()) { + throw new MissingResourcesException(); + } else { + throw new ConfigurationException(""" + BlueMap was not able to download some important resources! + Make sure BlueMap is able to connect to mojang-servers (%s).""" + .formatted(VersionManifest.DOMAIN), ex); + } + } + } + + return this.minecraftVersion; } public BlueMapConfiguration getConfig() { diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java index e56d53f2..69703f27 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderManagerImpl.java @@ -49,13 +49,13 @@ public RenderManagerImpl(BlueMapAPIImpl api, Plugin plugin) { @Override public boolean scheduleMapUpdateTask(BlueMapMap map, boolean force) { BlueMapMapImpl cmap = castMap(map); - return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), force)); + return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), s -> force)); } @Override public boolean scheduleMapUpdateTask(BlueMapMap map, Collection regions, boolean force) { BlueMapMapImpl cmap = castMap(map); - return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), regions, force)); + return renderManager.scheduleRenderTask(new MapUpdateTask(cmap.getBmMap(), regions, s -> force)); } @Override diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/BlueMapConfigManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/BlueMapConfigManager.java index eed564cd..aec0deb7 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/BlueMapConfigManager.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/BlueMapConfigManager.java @@ -29,9 +29,8 @@ import de.bluecolored.bluemap.common.config.storage.StorageConfig; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.resources.datapack.DataPack; +import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack; import de.bluecolored.bluemap.core.util.FileHelper; import de.bluecolored.bluemap.core.util.Key; import lombok.Builder; @@ -51,26 +50,26 @@ public class BlueMapConfigManager implements BlueMapConfiguration { private final ConfigManager configManager; - private final MinecraftVersion minecraftVersion; private final CoreConfig coreConfig; private final WebserverConfig webserverConfig; private final WebappConfig webappConfig; private final PluginConfig pluginConfig; private final Map mapConfigs; private final Map storageConfigs; - private final Path resourcePacksFolder; + private final Path packsFolder; + private final @Nullable String minecraftVersion; private final @Nullable Path modsFolder; @Builder private BlueMapConfigManager( - @NonNull MinecraftVersion minecraftVersion, @NonNull Path configRoot, + @Nullable String minecraftVersion, @Nullable Path defaultDataFolder, @Nullable Path defaultWebroot, @Nullable Collection autoConfigWorlds, @Nullable Boolean usePluginConfig, @Nullable Boolean useMetricsConfig, - @Nullable Path resourcePacksFolder, + @Nullable Path packsFolder, @Nullable Path modsFolder ) throws ConfigurationException { // set defaults @@ -79,10 +78,9 @@ private BlueMapConfigManager( if (autoConfigWorlds == null) autoConfigWorlds = Collections.emptyList(); if (usePluginConfig == null) usePluginConfig = true; if (useMetricsConfig == null) useMetricsConfig = true; - if (resourcePacksFolder == null) resourcePacksFolder = configRoot.resolve("resourcepacks"); + if (packsFolder == null) packsFolder = configRoot.resolve("packs"); // load - this.minecraftVersion = minecraftVersion; this.configManager = new ConfigManager(configRoot); this.coreConfig = loadCoreConfig(defaultDataFolder, useMetricsConfig); this.webappConfig = loadWebappConfig(defaultWebroot); @@ -90,7 +88,8 @@ private BlueMapConfigManager( this.pluginConfig = usePluginConfig ? loadPluginConfig() : new PluginConfig(); this.storageConfigs = Collections.unmodifiableMap(loadStorageConfigs(webappConfig.getWebroot())); this.mapConfigs = Collections.unmodifiableMap(loadMapConfigs(autoConfigWorlds)); - this.resourcePacksFolder = resourcePacksFolder; + this.packsFolder = packsFolder; + this.minecraftVersion = minecraftVersion; this.modsFolder = modsFolder; } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/Dialect.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/Dialect.java index 92539a7a..1aadf3be 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/Dialect.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/Dialect.java @@ -39,10 +39,10 @@ public interface Dialect extends Keyed { - Dialect MYSQL = new Impl(Key.bluemap("mysql"), MySQLCommandSet::new); - Dialect MARIADB = new Impl(Key.bluemap("mariadb"), MySQLCommandSet::new); - Dialect POSTGRESQL = new Impl(Key.bluemap("postgresql"), PostgreSQLCommandSet::new); - Dialect SQLITE = new Impl(Key.bluemap("sqlite"), SqliteCommandSet::new); + Dialect MYSQL = new Impl(Key.bluemap("mysql"), "jdbc:mysql:", MySQLCommandSet::new); + Dialect MARIADB = new Impl(Key.bluemap("mariadb"), "jdbc:mariadb:", MySQLCommandSet::new); + Dialect POSTGRESQL = new Impl(Key.bluemap("postgresql"), "jdbc:postgresql:", PostgreSQLCommandSet::new); + Dialect SQLITE = new Impl(Key.bluemap("sqlite"), "jdbc:sqlite:", SqliteCommandSet::new); Registry REGISTRY = new Registry<>( MYSQL, @@ -51,16 +51,23 @@ public interface Dialect extends Keyed { SQLITE ); + boolean supports(String connectionUrl); + CommandSet createCommandSet(Database database); @RequiredArgsConstructor class Impl implements Dialect { - @Getter - private final Key key; + @Getter private final Key key; + private final String protocol; private final Function commandSetProvider; + @Override + public boolean supports(String connectionUrl) { + return connectionUrl.startsWith(protocol); + } + @Override public CommandSet createCommandSet(Database database) { return commandSetProvider.apply(database); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/FileConfig.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/FileConfig.java index e4a4f1b5..5065504f 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/FileConfig.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/FileConfig.java @@ -41,6 +41,7 @@ public class FileConfig extends StorageConfig { private Path root = Path.of("bluemap", "web", "maps"); private String compression = Compression.GZIP.getKey().getFormatted(); + private boolean atomic = true; public Compression getCompression() throws ConfigurationException { return parseKey(Compression.REGISTRY, compression, "compression"); @@ -48,7 +49,7 @@ public Compression getCompression() throws ConfigurationException { @Override public FileStorage createStorage() throws ConfigurationException { - return new FileStorage(root, getCompression()); + return new FileStorage(root, getCompression(), atomic); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/SQLConfig.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/SQLConfig.java index 9dd7e8a8..68de7b2d 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/SQLConfig.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/config/storage/SQLConfig.java @@ -30,7 +30,6 @@ import de.bluecolored.bluemap.core.storage.sql.Database; import de.bluecolored.bluemap.core.storage.sql.SQLStorage; import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet; -import de.bluecolored.bluemap.core.util.Key; import lombok.AccessLevel; import lombok.Getter; import org.jetbrains.annotations.Nullable; @@ -46,16 +45,12 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; @SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"}) @ConfigSerializable @Getter public class SQLConfig extends StorageConfig { - private static final Pattern URL_DIALECT_PATTERN = Pattern.compile("jdbc:([^:]*):.*"); - private String connectionUrl = "jdbc:mysql://localhost/bluemap?permitMysqlScheme"; private Map connectionProperties = new HashMap<>(); @DebugDump private String dialect = null; @@ -101,14 +96,17 @@ public Dialect getDialect() throws ConfigurationException { // default from connection-url if (key == null) { - Matcher matcher = URL_DIALECT_PATTERN.matcher(connectionUrl); - if (!matcher.find()) { - throw new ConfigurationException(""" - Failed to parse the provided connection-url! + for (Dialect d : Dialect.REGISTRY.values()) { + if (d.supports(connectionUrl)) { + key = d.getKey().getFormatted(); + break; + } + } + + if (key == null) throw new ConfigurationException(""" + Could not find any sql-dialect that is matching the given connection-url. Please check your 'connection-url' setting in your configuration and make sure it is in the correct format. """.strip()); - } - key = Key.bluemap(matcher.group(1)).getFormatted(); } return parseKey(Dialect.REGISTRY, key, "dialect"); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index cabbc475..822e8944 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -44,7 +44,8 @@ import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.metrics.Metrics; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.MinecraftVersion; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.util.FileHelper; import de.bluecolored.bluemap.core.util.Tristate; @@ -124,7 +125,7 @@ private void load(@Nullable ResourcePack preloadedResourcePack) throws IOExcepti BlueMapConfigManager configManager = BlueMapConfigManager.builder() .minecraftVersion(serverInterface.getMinecraftVersion()) .configRoot(serverInterface.getConfigFolder()) - .resourcePacksFolder(serverInterface.getConfigFolder().resolve("resourcepacks")) + .packsFolder(serverInterface.getConfigFolder().resolve("packs")) .modsFolder(serverInterface.getModsFolder().orElse(null)) .useMetricsConfig(serverInterface.isMetricsEnabled() == Tristate.UNDEFINED) .autoConfigWorlds(serverInterface.getLoadedServerWorlds()) @@ -287,7 +288,7 @@ public void run() { save(); } }; - daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(2), TimeUnit.MINUTES.toMillis(2)); + daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(10), TimeUnit.MINUTES.toMillis(10)); //periodically save markers int writeMarkersInterval = pluginConfig.getWriteMarkersInterval(); @@ -341,11 +342,12 @@ public void run() { } //metrics + MinecraftVersion minecraftVersion = blueMap.getOrLoadMinecraftVersion(); TimerTask metricsTask = new TimerTask() { @Override public void run() { if (serverInterface.isMetricsEnabled().getOr(coreConfig::isMetrics)) - Metrics.sendReport(implementationType, configManager.getMinecraftVersion().getVersionString()); + Metrics.sendReport(implementationType, minecraftVersion.getId()); } }; daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30)); @@ -389,12 +391,11 @@ public void run() { public void unload() { this.unload(false); } + public void unload(boolean keepWebserver) { loadingLock.interruptAndLock(); try { synchronized (this) { - //save - save(); //disable api if (api != null) api.unregister(); @@ -415,8 +416,18 @@ public void unload(boolean keepWebserver) { } regionFileWatchServices = null; - //stop services + // stop render-manager if (renderManager != null){ + if (renderManager.getCurrentRenderTask() != null) { + renderManager.removeAllRenderTasks(); + if (!renderManager.isRunning()) renderManager.start(1); + try { + renderManager.awaitIdle(true); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + renderManager.stop(); try { renderManager.awaitShutdown(); @@ -424,8 +435,11 @@ public void unload(boolean keepWebserver) { Thread.currentThread().interrupt(); } } - renderManager = null; + //save + save(); + + // stop webserver if (webServer != null && !keepWebserver) { try { webServer.close(); @@ -435,7 +449,7 @@ public void unload(boolean keepWebserver) { webServer = null; } - if (webLogger != null) { + if (webLogger != null && !keepWebserver) { try { webLogger.close(); } catch (Exception ex) { diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java index 6842addd..21a7324b 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java @@ -67,7 +67,11 @@ public RegionFileWatchService(RenderManager renderManager, BmMap map) throws IOE FileHelper.createDirectories(folder); this.watchService = folder.getFileSystem().newWatchService(); - folder.register(this.watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY); + folder.register(this.watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_DELETE + ); Logger.global.logDebug("Created region-file watch-service for map '" + map.getId() + "' at '" + folder + "'."); } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index 4811a41a..26a16017 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.common.plugin.commands; +import com.flowpowered.math.vector.Vector2d; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3i; @@ -47,14 +48,15 @@ import de.bluecolored.bluemap.common.rendermanager.*; import de.bluecolored.bluemap.common.serverinterface.CommandSource; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.debug.StateDumper; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; -import de.bluecolored.bluemap.core.map.MapRenderState; +import de.bluecolored.bluemap.core.map.renderstate.TileInfoRegion; import de.bluecolored.bluemap.core.storage.MapStorage; import de.bluecolored.bluemap.core.storage.Storage; +import de.bluecolored.bluemap.core.util.Grid; import de.bluecolored.bluemap.core.world.Chunk; +import de.bluecolored.bluemap.core.world.ChunkConsumer; import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.block.Block; import de.bluecolored.bluemap.core.world.block.entity.BlockEntity; @@ -123,6 +125,15 @@ public void init() { .then(argument("z", DoubleArgumentType.doubleArg()) .executes(this::debugBlockCommand)))))) + .then(literal("map") + .requires(requirements("bluemap.debug")) + .then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin)) + .executes(this::debugMapCommand) + + .then(argument("x", IntegerArgumentType.integer()) + .then(argument("z", IntegerArgumentType.integer()) + .executes(this::debugMapCommand))))) + .then(literal("flush") .requires(requirements("bluemap.debug")) .executes(this::debugFlushCommand) @@ -329,32 +340,27 @@ public int versionCommand(CommandContext context) { renderThreadCount = plugin.getRenderManager().getWorkerThreadCount(); } - MinecraftVersion minecraftVersion = plugin.getServerInterface().getMinecraftVersion(); + String minecraftVersion = plugin.getServerInterface().getMinecraftVersion(); source.sendMessage(Text.of(TextFormat.BOLD, TextColor.BLUE, "Version: ", TextColor.WHITE, BlueMap.VERSION)); source.sendMessage(Text.of(TextColor.GRAY, "Commit: ", TextColor.WHITE, BlueMap.GIT_HASH)); source.sendMessage(Text.of(TextColor.GRAY, "Implementation: ", TextColor.WHITE, plugin.getImplementationType())); - source.sendMessage(Text.of( - TextColor.GRAY, "Minecraft compatibility: ", TextColor.WHITE, minecraftVersion.getVersionString(), - TextColor.GRAY, " (" + minecraftVersion.getResource().getVersion().getVersionString() + ")" - )); + source.sendMessage(Text.of(TextColor.GRAY, "Minecraft: ", TextColor.WHITE, minecraftVersion)); source.sendMessage(Text.of(TextColor.GRAY, "Render-threads: ", TextColor.WHITE, renderThreadCount)); source.sendMessage(Text.of(TextColor.GRAY, "Available processors: ", TextColor.WHITE, Runtime.getRuntime().availableProcessors())); source.sendMessage(Text.of(TextColor.GRAY, "Available memory: ", TextColor.WHITE, (Runtime.getRuntime().maxMemory() / 1024L / 1024L) + " MiB")); - if (minecraftVersion.isAtLeast(new MinecraftVersion(1, 15))) { - String clipboardValue = - "Version: " + BlueMap.VERSION + "\n" + - "Commit: " + BlueMap.GIT_HASH + "\n" + - "Implementation: " + plugin.getImplementationType() + "\n" + - "Minecraft compatibility: " + minecraftVersion.getVersionString() + " (" + minecraftVersion.getResource().getVersion().getVersionString() + ")\n" + - "Render-threads: " + renderThreadCount + "\n" + - "Available processors: " + Runtime.getRuntime().availableProcessors() + "\n" + - "Available memory: " + Runtime.getRuntime().maxMemory() / 1024L / 1024L + " MiB"; - source.sendMessage(Text.of(TextColor.DARK_GRAY, "[copy to clipboard]") - .setClickAction(Text.ClickAction.COPY_TO_CLIPBOARD, clipboardValue) - .setHoverText(Text.of(TextColor.GRAY, "click to copy the above text .. ", TextFormat.ITALIC, TextColor.GRAY, "duh!"))); - } + String clipboardValue = + "Version: " + BlueMap.VERSION + "\n" + + "Commit: " + BlueMap.GIT_HASH + "\n" + + "Implementation: " + plugin.getImplementationType() + "\n" + + "Minecraft: " + minecraftVersion + "\n" + + "Render-threads: " + renderThreadCount + "\n" + + "Available processors: " + Runtime.getRuntime().availableProcessors() + "\n" + + "Available memory: " + Runtime.getRuntime().maxMemory() / 1024L / 1024L + " MiB"; + source.sendMessage(Text.of(TextColor.DARK_GRAY, "[copy to clipboard]") + .setClickAction(Text.ClickAction.COPY_TO_CLIPBOARD, clipboardValue) + .setHoverText(Text.of(TextColor.GRAY, "click to copy the above text .. ", TextFormat.ITALIC, TextColor.GRAY, "duh!"))); return 1; } @@ -467,6 +473,86 @@ public int debugFlushCommand(CommandContext context) { return 1; } + public int debugMapCommand(CommandContext context) { + final CommandSource source = commandSourceInterface.apply(context.getSource()); + + // parse arguments + String mapId = context.getArgument("map", String.class); + Optional x = getOptionalArgument(context, "x", Integer.class); + Optional z = getOptionalArgument(context, "z", Integer.class); + + final BmMap map = parseMap(mapId).orElse(null); + if (map == null) { + source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this id: ", TextColor.WHITE, mapId)); + return 0; + } + + final Vector2i position; + if (x.isPresent() && z.isPresent()) { + position = new Vector2i(x.get(), z.get()); + } else { + position = source.getPosition() + .map(v -> v.toVector2(true)) + .map(Vector2d::floor) + .map(Vector2d::toInt) + .orElse(null); + + if (position == null) { + source.sendMessage(Text.of(TextColor.RED, "Can't detect a location from this command-source, you'll have to define a position!")); + return 0; + } + } + + new Thread(() -> { + // collect and output debug info + Grid chunkGrid = map.getWorld().getChunkGrid(); + Grid regionGrid = map.getWorld().getRegionGrid(); + Grid tileGrid = map.getHiresModelManager().getTileGrid(); + + Vector2i regionPos = regionGrid.getCell(position); + Vector2i chunkPos = chunkGrid.getCell(position); + Vector2i tilePos = tileGrid.getCell(position); + + TileInfoRegion.TileInfo tileInfo = map.getMapTileState().get(tilePos.getX(), tilePos.getY()); + + int lastChunkHash = map.getMapChunkState().get(chunkPos.getX(), chunkPos.getY()); + int currentChunkHash = 0; + + class FindHashConsumer implements ChunkConsumer.ListOnly { + public int timestamp = 0; + + @Override + public void accept(int chunkX, int chunkZ, int timestamp) { + if (chunkPos.getX() == chunkX && chunkPos.getY() == chunkZ) + this.timestamp = timestamp; + } + } + + try { + FindHashConsumer findHashConsumer = new FindHashConsumer(); + map.getWorld().getRegion(regionPos.getX(), regionPos.getY()) + .iterateAllChunks(findHashConsumer); + currentChunkHash = findHashConsumer.timestamp; + } catch (IOException e) { + Logger.global.logError("Failed to load chunk-hash.", e); + } + + Map lines = new LinkedHashMap<>(); + lines.put("region-pos", regionPos); + lines.put("chunk-pos", chunkPos); + lines.put("chunk-curr-hash", currentChunkHash); + lines.put("chunk-last-hash", lastChunkHash); + lines.put("tile-pos", tilePos); + lines.put("tile-render-time", tileInfo.getRenderTime()); + lines.put("tile-state", tileInfo.getState().getKey().getFormatted()); + + source.sendMessage(Text.of(TextColor.GOLD, "Map tile info:")); + source.sendMessage(formatMap(lines)); + }, "BlueMap-Plugin-DebugMapCommand").start(); + + return 1; + } + public int debugBlockCommand(CommandContext context) { final CommandSource source = commandSourceInterface.apply(context.getSource()); @@ -523,7 +609,7 @@ private Text formatBlock(Block block) { lines.put("chunk-has-lightdata", chunk.hasLightData()); lines.put("chunk-inhabited-time", chunk.getInhabitedTime()); lines.put("block-state", block.getBlockState()); - lines.put("biome", block.getBiomeId()); + lines.put("biome", block.getBiome().getKey()); lines.put("position", block.getX() + " | " + block.getY() + " | " + block.getZ()); lines.put("block-light", block.getBlockLightLevel()); lines.put("sun-light", block.getSunLightLevel()); @@ -533,6 +619,10 @@ private Text formatBlock(Block block) { lines.put("block-entity", blockEntity); } + return formatMap(lines); + } + + private Text formatMap(Map lines) { Object[] textElements = lines.entrySet().stream() .flatMap(e -> Stream.of(TextColor.GRAY, e.getKey(), ": ", TextColor.WHITE, e.getValue(), "\n")) .toArray(Object[]::new); @@ -754,14 +844,9 @@ public int updateCommand(CommandContext context, boolean force) { } for (BmMap map : maps) { - MapUpdateTask updateTask = new MapUpdateTask(map, center, radius); + MapUpdateTask updateTask = new MapUpdateTask(map, center, radius, s -> force); plugin.getRenderManager().scheduleRenderTask(updateTask); - if (force) { - MapRenderState state = map.getRenderState(); - updateTask.getRegions().forEach(region -> state.setRenderTime(region, -1)); - } - source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + map.getId() + "' ", TextColor.GRAY, "(" + updateTask.getRegions().size() + " regions, ~" + updateTask.getRegions().size() * 1024L + " chunks)")); } @@ -874,7 +959,7 @@ public int mapsCommand(CommandContext context) { lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0World: ", TextColor.DARK_GRAY, map.getWorld().getId())); lines.add(Text.of(TextColor.GRAY, "\u00A0\u00A0\u00A0Last Update: ", - TextColor.DARK_GRAY, helper.formatTime(map.getRenderState().getLatestRenderTime()))); + TextColor.DARK_GRAY, helper.formatTime(map.getMapTileState().getLastRenderTime() * 1000L))); if (frozen) lines.add(Text.of(TextColor.AQUA, TextFormat.ITALIC, "\u00A0\u00A0\u00A0This map is frozen!")); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/CombinedRenderTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/CombinedRenderTask.java index 6a2e8969..f239f60f 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/CombinedRenderTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/CombinedRenderTask.java @@ -84,13 +84,10 @@ public void cancel() { public boolean contains(RenderTask task) { if (this.equals(task)) return true; - if (task instanceof CombinedRenderTask) { - CombinedRenderTask combinedTask = (CombinedRenderTask) task; - + if (task instanceof CombinedRenderTask combinedTask) { for (RenderTask subTask : combinedTask.tasks) { if (!this.contains(subTask)) return false; } - return true; } @@ -111,4 +108,5 @@ public Optional getDetail() { if (this.currentTaskIndex >= this.tasks.size()) return Optional.empty(); return Optional.ofNullable(this.tasks.get(this.currentTaskIndex).getDescription()); } + } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java index 0e950645..20f6a691 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java @@ -55,19 +55,15 @@ public void doWork() throws Exception { // save lowres-tile-manager to clear/flush any buffered data this.map.getLowresTileManager().save(); - try { - // purge the map - map.getStorage().delete(progress -> { - this.progress = progress; - return !this.cancelled; - }); + // purge the map + map.getStorage().delete(progress -> { + this.progress = progress; + return !this.cancelled; + }); - // reset texture gallery - map.resetTextureGallery(); - } finally { - // reset renderstate - map.getRenderState().reset(); - } + map.resetTextureGallery(); + map.getMapTileState().reset(); + map.getMapChunkState().reset(); } @Override diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java index 4f19ab21..d5224cdd 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapUpdateTask.java @@ -26,16 +26,18 @@ import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.api.debug.DebugDump; +import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.map.renderstate.MapTileState; +import de.bluecolored.bluemap.core.map.renderstate.TileState; +import de.bluecolored.bluemap.core.storage.GridStorage; import de.bluecolored.bluemap.core.util.Grid; import de.bluecolored.bluemap.core.world.World; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.io.IOException; +import java.util.*; import java.util.function.Predicate; -import java.util.stream.Collectors; +import java.util.stream.Stream; @DebugDump public class MapUpdateTask extends CombinedRenderTask { @@ -47,7 +49,7 @@ public MapUpdateTask(BmMap map) { this(map, getRegions(map)); } - public MapUpdateTask(BmMap map, boolean force) { + public MapUpdateTask(BmMap map, Predicate force) { this(map, getRegions(map), force); } @@ -55,15 +57,15 @@ public MapUpdateTask(BmMap map, Vector2i center, int radius) { this(map, getRegions(map, center, radius)); } - public MapUpdateTask(BmMap map, Vector2i center, int radius, boolean force) { + public MapUpdateTask(BmMap map, Vector2i center, int radius, Predicate force) { this(map, getRegions(map, center, radius), force); } public MapUpdateTask(BmMap map, Collection regions) { - this(map, regions, false); + this(map, regions, s -> false); } - public MapUpdateTask(BmMap map, Collection regions, boolean force) { + public MapUpdateTask(BmMap map, Collection regions, Predicate force) { super("Update map '" + map.getId() + "'", createTasks(map, regions, force)); this.map = map; this.regions = Collections.unmodifiableCollection(new ArrayList<>(regions)); @@ -77,7 +79,7 @@ public Collection getRegions() { return regions; } - private static Collection createTasks(BmMap map, Collection regions, boolean force) { + private static Collection createTasks(BmMap map, Collection regions, Predicate force) { ArrayList regionTasks = new ArrayList<>(regions.size()); regions.forEach(region -> regionTasks.add(new WorldRegionRenderTask(map, region, force))); @@ -99,33 +101,47 @@ private static Collection createTasks(BmMap map, Collection getRegions(BmMap map) { + private static Collection getRegions(BmMap map) { return getRegions(map, null, -1); } - private static List getRegions(BmMap map, Vector2i center, int radius) { + private static Collection getRegions(BmMap map, Vector2i center, int radius) { World world = map.getWorld(); Grid regionGrid = world.getRegionGrid(); - Predicate regionFilter = map.getMapSettings().getRenderBoundariesCellFilter(regionGrid); + Predicate regionBoundsFilter = map.getMapSettings().getCellRenderBoundariesFilter(regionGrid, true); + Predicate regionRadiusFilter; if (center == null || radius < 0) { - return world.listRegions().stream() - .filter(regionFilter) - .collect(Collectors.toList()); + regionRadiusFilter = r -> true; + } else { + Vector2i halfCell = regionGrid.getGridSize().div(2); + long increasedRadiusSquared = (long) Math.pow(radius + Math.ceil(halfCell.length()), 2); + regionRadiusFilter = r -> { + Vector2i min = regionGrid.getCellMin(r); + Vector2i regionCenter = min.add(halfCell); + return regionCenter.toLong().distanceSquared(center.toLong()) <= increasedRadiusSquared; + }; } - List regions = new ArrayList<>(); - Vector2i halfCell = regionGrid.getGridSize().div(2); - long increasedRadiusSquared = (long) Math.pow(radius + Math.ceil(halfCell.length()), 2); + Set regions = new HashSet<>(); - for (Vector2i region : world.listRegions()) { - if (!regionFilter.test(region)) continue; + // update all regions in the world-files + world.listRegions().stream() + .filter(regionBoundsFilter) + .filter(regionRadiusFilter) + .forEach(regions::add); - Vector2i min = regionGrid.getCellMin(region); - Vector2i regionCenter = min.add(halfCell); - - if (regionCenter.toLong().distanceSquared(center.toLong()) <= increasedRadiusSquared) - regions.add(region); + // also update regions that are present as map-tile-state files (they might have been rendered before but deleted now) + Grid tileGrid = map.getHiresModelManager().getTileGrid(); + Grid cellGrid = MapTileState.GRID.multiply(tileGrid); + try (Stream stream = map.getStorage().tileState().stream()) { + stream + .map(c -> new Vector2i(c.getX(), c.getZ())) + .flatMap(v -> cellGrid.getIntersecting(v, regionGrid).stream()) + .filter(regionRadiusFilter) + .forEach(regions::add); + } catch (IOException ex) { + Logger.global.logError("Failed to load map tile state!", ex); } return regions; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderManager.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderManager.java index 222a54fa..d5d6112f 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderManager.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/RenderManager.java @@ -106,9 +106,23 @@ public boolean isRunning() { } public void awaitIdle() throws InterruptedException { + awaitIdle(false); + } + + public void awaitIdle(boolean log) throws InterruptedException { synchronized (this.renderTasks) { - while (!this.renderTasks.isEmpty()) - this.renderTasks.wait(10000); + while (!this.renderTasks.isEmpty()) { + this.renderTasks.wait(5000); + + if (log) { + RenderTask task = this.getCurrentRenderTask(); + if (task != null) { + Logger.global.logInfo("Waiting for task '" + task.getDescription() + "' to stop.. (" + + (Math.round(task.estimateProgress() * 10000) / 100.0) + "%)"); + } + } + + } } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java index 5359ef7f..33f8862a 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/WorldRegionRenderTask.java @@ -29,205 +29,246 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.ActionAndNextState; +import de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.BoundsSituation; +import de.bluecolored.bluemap.core.map.renderstate.TileInfoRegion; +import de.bluecolored.bluemap.core.map.renderstate.TileState; import de.bluecolored.bluemap.core.util.Grid; import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.ChunkConsumer; -import de.bluecolored.bluemap.core.world.Region; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.*; +import java.util.Comparator; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; -import java.util.stream.Collectors; + +import static de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.Action.DELETE; +import static de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.Action.RENDER; @DebugDump public class WorldRegionRenderTask implements RenderTask { - private final BmMap map; - private final Vector2i worldRegion; - private final boolean force; + @Getter private final BmMap map; + @Getter private final Vector2i regionPos; + @Getter private final Predicate force; - private Deque tiles; - private int tileCount; - private long startTime; + private Grid regionGrid, chunkGrid, tileGrid; + private Vector2i chunkMin, chunkMax, chunksSize; + private Vector2i tileMin, tileMax, tileSize; + private int[] chunkHashes; + private ActionAndNextState[] tileActions; + + private volatile int nextTileX, nextTileZ; private volatile int atWork; - private volatile boolean cancelled; + private volatile boolean completed, cancelled; - public WorldRegionRenderTask(BmMap map, Vector2i worldRegion) { - this(map, worldRegion, false); + public WorldRegionRenderTask(BmMap map, Vector2i regionPos) { + this(map, regionPos, false); } - public WorldRegionRenderTask(BmMap map, Vector2i worldRegion, boolean force) { + public WorldRegionRenderTask(BmMap map, Vector2i regionPos, boolean force) { + this(map, regionPos, s -> force); + } + + public WorldRegionRenderTask(BmMap map, Vector2i regionPos, Predicate force) { this.map = map; - this.worldRegion = worldRegion; + this.regionPos = regionPos; this.force = force; - this.tiles = null; - this.tileCount = -1; - this.startTime = -1; + this.nextTileX = 0; + this.nextTileZ = 0; this.atWork = 0; + this.completed = false; this.cancelled = false; } private synchronized void init() { - Set tileSet = new HashSet<>(); - startTime = System.currentTimeMillis(); - // collect chunks - long changesSince = force ? 0 : map.getRenderState().getRenderTime(worldRegion); - Region region = map.getWorld().getRegion(worldRegion.getX(), worldRegion.getY()); - Collection chunks = new ArrayList<>(1024); + // calculate bounds + this.regionGrid = map.getWorld().getRegionGrid(); + this.chunkGrid = map.getWorld().getChunkGrid(); + this.tileGrid = map.getHiresModelManager().getTileGrid(); + this.chunkMin = regionGrid.getCellMin(regionPos, chunkGrid); + this.chunkMax = regionGrid.getCellMax(regionPos, chunkGrid); + this.chunksSize = chunkMax.sub(chunkMin).add(1, 1); + this.tileMin = regionGrid.getCellMin(regionPos, tileGrid); + this.tileMax = regionGrid.getCellMax(regionPos, tileGrid); + this.tileSize = tileMax.sub(tileMin).add(1, 1); + + // load chunk-hash array + int chunkMaxCount = chunksSize.getX() * chunksSize.getY(); try { - region.iterateAllChunks((ChunkConsumer.ListOnly) (x, z, timestamp) -> { - if (timestamp >= changesSince) chunks.add(new Vector2i(x, z)); - }); + chunkHashes = new int[chunkMaxCount]; + map.getWorld().getRegion(regionPos.getX(), regionPos.getY()) + .iterateAllChunks( (ChunkConsumer.ListOnly) (x, z, timestamp) -> { + chunkHashes[chunkIndex( + x - chunkMin.getX(), + z - chunkMin.getY() + )] = timestamp; + map.getWorld().invalidateChunkCache(x, z); + }); } catch (IOException ex) { - Logger.global.logWarning("Failed to read region " + worldRegion + " from world " + map.getWorld().getName() + " (" + ex + ")"); + Logger.global.logError("Failed to load chunks for region " + regionPos, ex); + cancel(); } - Grid tileGrid = map.getHiresModelManager().getTileGrid(); - Grid chunkGrid = map.getWorld().getChunkGrid(); - Predicate boundsTileFilter = map.getMapSettings().getRenderBoundariesCellFilter(tileGrid); + // check tile actions + int tileMaxCount = tileSize.getX() * tileSize.getY(); + int tileRenderCount = 0; + int tileDeleteCount = 0; + tileActions = new ActionAndNextState[tileMaxCount]; + for (int x = 0; x < tileSize.getX(); x++) { + for (int z = 0; z < tileSize.getY(); z++) { + Vector2i tile = new Vector2i(tileMin.getX() + x, tileMin.getY() + z); + TileState tileState = map.getMapTileState().get(tile.getX(), tile.getY()).getState(); - for (Vector2i chunk : chunks) { - Vector2i tileMin = chunkGrid.getCellMin(chunk, tileGrid); - Vector2i tileMax = chunkGrid.getCellMax(chunk, tileGrid); + int tileIndex = tileIndex(x, z); + tileActions[tileIndex] = tileState.findActionAndNextState( + force.test(tileState) || checkChunksHaveChanges(tile), + checkTileBounds(tile) + ); - for (int x = tileMin.getX(); x <= tileMax.getX(); x++) { - for (int z = tileMin.getY(); z <= tileMax.getY(); z++) { - tileSet.add(new Vector2l(x, z)); - } + if (tileActions[tileIndex].action() == RENDER) + tileRenderCount++; + if (tileActions[tileIndex].action() == DELETE) + tileDeleteCount++; } - - // make sure chunk gets re-loaded from disk - map.getWorld().invalidateChunkCache(chunk.getX(), chunk.getY()); } - this.tileCount = tileSet.size(); - this.tiles = tileSet.stream() - .sorted(WorldRegionRenderTask::compareVec2L) //sort with longs to avoid overflow (comparison uses distanceSquared) - .map(Vector2l::toInt) // back to ints - .filter(boundsTileFilter) - .filter(map.getTileFilter()) - .collect(Collectors.toCollection(ArrayDeque::new)); + if (tileRenderCount >= tileMaxCount * 0.75) + map.getWorld().preloadRegionChunks(regionPos.getX(), regionPos.getY()); + + if (tileRenderCount + tileDeleteCount == 0) + completed = true; - if (tiles.isEmpty()) complete(); - else { - // preload chunks - map.getWorld().preloadRegionChunks(worldRegion.getX(), worldRegion.getY()); - } } @Override public void doWork() { - if (cancelled) return; + if (cancelled || completed) return; - Vector2i tile; + int tileX, tileZ; synchronized (this) { - if (tiles == null) init(); - if (tiles.isEmpty()) return; + if (cancelled || completed) return; - tile = tiles.pollFirst(); + tileX = nextTileX; + tileZ = nextTileZ; + + if (tileX == 0 && tileZ == 0) { + init(); + if (cancelled || completed) return; + } + + nextTileX = tileX + 1; + if (nextTileX >= tileSize.getX()) { + nextTileZ = tileZ + 1; + nextTileX = 0; + } + if (nextTileZ >= tileSize.getY()) { + completed = true; + } this.atWork++; } - if (tileRenderPreconditions(tile)) { - map.renderTile(tile); // <- actual work - } + processTile(tileX, tileZ); synchronized (this) { this.atWork--; - if (atWork <= 0 && tiles.isEmpty() && !cancelled) { + if (atWork <= 0 && completed && !cancelled) { complete(); } } } - private boolean tileRenderPreconditions(Vector2i tile) { - Grid tileGrid = map.getHiresModelManager().getTileGrid(); - Grid chunkGrid = map.getWorld().getChunkGrid(); + private void processTile(int x, int z) { + Vector2i tile = new Vector2i(tileMin.getX() + x, tileMin.getY() + z); + ActionAndNextState action = tileActions[tileIndex(x, z)]; + TileState resultState = TileState.RENDER_ERROR; - Vector2i minChunk = tileGrid.getCellMin(tile, chunkGrid); - Vector2i maxChunk = tileGrid.getCellMax(tile, chunkGrid); + try { - long minInhab = map.getMapSettings().getMinInhabitedTime(); - int minInhabRadius = map.getMapSettings().getMinInhabitedTimeRadius(); - if (minInhabRadius < 0) minInhabRadius = 0; - if (minInhabRadius > 16) minInhabRadius = 16; // sanity check - boolean isInhabited = false; + resultState = switch (action.action()) { - for (int x = minChunk.getX(); x <= maxChunk.getX(); x++) { - for (int z = minChunk.getY(); z <= maxChunk.getY(); z++) { - Chunk chunk = map.getWorld().getChunk(x, z); - if (!chunk.isGenerated()) return false; - if (!chunk.hasLightData() && !map.getMapSettings().isIgnoreMissingLightData()) return false; - if (chunk.getInhabitedTime() >= minInhab) isInhabited = true; - } - } + case NONE -> action.state(); - if (minInhabRadius > 0 && !isInhabited) { - for (int x = minChunk.getX() - minInhabRadius; x <= maxChunk.getX() + minInhabRadius; x++) { - for (int z = minChunk.getY() - minInhabRadius; z <= maxChunk.getY() + minInhabRadius; z++) { - Chunk chunk = map.getWorld().getChunk(x, z); - if (chunk.getInhabitedTime() >= minInhab) { - isInhabited = true; - break; + case RENDER -> { + TileState failedState = checkTileRenderPreconditions(tile); + if (failedState != null){ + map.unrenderTile(tile); + yield failedState; } + + map.renderTile(tile); + yield action.state(); } - } + + case DELETE -> { + map.unrenderTile(tile); + yield action.state(); + } + + }; + + } catch (Exception ex) { + + Logger.global.logError("Error while processing map-tile " + tile + " for map '" + map.getId() + "'", ex); + + } finally { + + // mark tile with new state + map.getMapTileState().set(tile.getX(), tile.getY(), new TileInfoRegion.TileInfo( + (int) (System.currentTimeMillis() / 1000), + resultState + )); + } - return isInhabited; } - private void complete() { - map.getRenderState().setRenderTime(worldRegion, startTime); + private synchronized void complete() { + // save chunk-hashes + if (chunkHashes != null) { + for (int x = 0; x < chunksSize.getX(); x++) { + for (int z = 0; z < chunksSize.getY(); z++) { + int hash = chunkHashes[chunkIndex(x, z)]; + map.getMapChunkState().set(chunkMin.getX() + x, chunkMin.getY() + z, hash); + } + } + chunkHashes = null; + } + + // save map (at most, every minute) + map.save(TimeUnit.MINUTES.toMillis(1)); } @Override @DebugDump public synchronized boolean hasMoreWork() { - return !cancelled && (tiles == null || !tiles.isEmpty()); + return !completed && !cancelled; } @Override @DebugDump public double estimateProgress() { - if (tiles == null) return 0; - if (tileCount == 0) return 1; - - double remainingTiles = tiles.size(); - return 1 - (remainingTiles / this.tileCount); + if (tileSize == null) return 0; + return Math.min((double) (nextTileZ * tileSize.getX() + nextTileX) / (tileSize.getX() * tileSize.getY()), 1); } @Override public void cancel() { this.cancelled = true; - - synchronized (this) { - if (tiles != null) this.tiles.clear(); - } - } - - public BmMap getMap() { - return map; - } - - public Vector2i getWorldRegion() { - return worldRegion; - } - - public boolean isForce() { - return force; } @Override public String getDescription() { - return "Update region " + getWorldRegion() + " for map '" + map.getId() + "'"; + return "Update region " + regionPos + " for map '" + map.getId() + "'"; } @Override @@ -235,19 +276,101 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WorldRegionRenderTask that = (WorldRegionRenderTask) o; - return force == that.force && map.getId().equals(that.map.getId()) && worldRegion.equals(that.worldRegion); + return force == that.force && map.getId().equals(that.map.getId()) && regionPos.equals(that.regionPos); } @Override public int hashCode() { - return worldRegion.hashCode(); + return regionPos.hashCode(); + } + + private int chunkIndex(int x, int z) { + return z * chunksSize.getX() + x; + } + + private int tileIndex(int x, int z) { + return z * tileSize.getX() + x; + } + + private boolean checkChunksHaveChanges(Vector2i tile) { + int minX = tileGrid.getCellMinX(tile.getX(), chunkGrid), + maxX = tileGrid.getCellMaxX(tile.getX(), chunkGrid), + minZ = tileGrid.getCellMinY(tile.getY(), chunkGrid), + maxZ = tileGrid.getCellMaxY(tile.getY(), chunkGrid); + + for (int chunkX = minX; chunkX <= maxX; chunkX++) { + for (int chunkZ = minZ; chunkZ <= maxZ; chunkZ++) { + int dx = chunkX - chunkMin.getX(); + int dz = chunkZ - chunkMin.getY(); + + // only check hash for chunks inside the current region + if ( + chunkX >= chunkMin.getX() && chunkX <= chunkMax.getX() && + chunkZ >= chunkMin.getY() && chunkZ <= chunkMax.getY() + ) { + int hash = chunkHashes[chunkIndex(dx, dz)]; + int lastHash = map.getMapChunkState().get(chunkX, chunkZ); + + if (lastHash != hash) return true; + } + } + } + + return false; + } + + private BoundsSituation checkTileBounds(Vector2i tile) { + boolean isInsideBounds = map.getMapSettings().isInsideRenderBoundaries(tile, tileGrid, true); + if (!isInsideBounds) return BoundsSituation.OUTSIDE; + + boolean isFullyInsideBounds = map.getMapSettings().isInsideRenderBoundaries(tile, tileGrid, false); + return isFullyInsideBounds ? BoundsSituation.INSIDE : BoundsSituation.EDGE; + } + + private @Nullable TileState checkTileRenderPreconditions(Vector2i tile) { + boolean chunksAreInhabited = false; + + long minInhabitedTime = map.getMapSettings().getMinInhabitedTime(); + int minInhabitedTimeRadius = map.getMapSettings().getMinInhabitedTimeRadius(); + boolean requireLight = !map.getMapSettings().isIgnoreMissingLightData(); + + int minX = tileGrid.getCellMinX(tile.getX(), chunkGrid), + maxX = tileGrid.getCellMaxX(tile.getX(), chunkGrid), + minZ = tileGrid.getCellMinY(tile.getY(), chunkGrid), + maxZ = tileGrid.getCellMaxY(tile.getY(), chunkGrid); + + for (int chunkX = minX; chunkX <= maxX; chunkX++) { + for (int chunkZ = minZ; chunkZ <= maxZ; chunkZ++) { + Chunk chunk = map.getWorld().getChunk(chunkX, chunkZ); + if (chunk == Chunk.ERRORED_CHUNK) return TileState.CHUNK_ERROR; + if (!chunk.isGenerated()) return TileState.NOT_GENERATED; + if (requireLight && !chunk.hasLightData()) return TileState.MISSING_LIGHT; + if (chunk.getInhabitedTime() >= minInhabitedTime) chunksAreInhabited = true; + } + } + + // second pass for increased inhabited-time-radius + if (!chunksAreInhabited && minInhabitedTimeRadius > 0) { + inhabitedRadiusCheck: + for (int chunkX = minX - minInhabitedTimeRadius; chunkX <= maxX + minInhabitedTimeRadius; chunkX++) { + for (int chunkZ = minZ - minInhabitedTimeRadius; chunkZ <= maxZ + minInhabitedTimeRadius; chunkZ++) { + Chunk chunk = map.getWorld().getChunk(chunkX, chunkZ); + if (chunk.getInhabitedTime() >= minInhabitedTime) { + chunksAreInhabited = true; + break inhabitedRadiusCheck; + } + } + } + } + + return chunksAreInhabited ? null : TileState.LOW_INHABITED_TIME; } public static Comparator defaultComparator(final Vector2i centerRegion) { return (task1, task2) -> { // use long to compare to avoid overflow (comparison uses distanceSquared) - Vector2l task1Rel = new Vector2l(task1.worldRegion.getX() - centerRegion.getX(), task1.worldRegion.getY() - centerRegion.getY()); - Vector2l task2Rel = new Vector2l(task2.worldRegion.getX() - centerRegion.getX(), task2.worldRegion.getY() - centerRegion.getY()); + Vector2l task1Rel = new Vector2l(task1.regionPos.getX() - centerRegion.getX(), task1.regionPos.getY() - centerRegion.getY()); + Vector2l task2Rel = new Vector2l(task2.regionPos.getX() - centerRegion.getX(), task2.regionPos.getY() - centerRegion.getY()); return compareVec2L(task1Rel, task2Rel); }; } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/serverinterface/Server.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/serverinterface/Server.java index 1a853a13..1b00db5b 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/serverinterface/Server.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/serverinterface/Server.java @@ -25,10 +25,10 @@ package de.bluecolored.bluemap.common.serverinterface; import de.bluecolored.bluemap.api.debug.DebugDump; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.util.Tristate; import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.mca.MCAWorld; +import org.jetbrains.annotations.Nullable; import java.nio.file.Path; import java.util.Collection; @@ -37,7 +37,7 @@ public interface Server { @DebugDump - MinecraftVersion getMinecraftVersion(); + @Nullable String getMinecraftVersion(); /** * Returns the Folder containing the configurations for the plugin diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/LoggingRequestHandler.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/LoggingRequestHandler.java index e71ad860..f5483457 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/LoggingRequestHandler.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/LoggingRequestHandler.java @@ -27,8 +27,10 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.common.web.http.*; import de.bluecolored.bluemap.core.logger.Logger; +import lombok.Getter; @DebugDump +@Getter public class LoggingRequestHandler implements HttpRequestHandler { private final HttpRequestHandler delegate; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/RoutingRequestHandler.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/RoutingRequestHandler.java index b21f6e31..0f2f8cd4 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/RoutingRequestHandler.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/RoutingRequestHandler.java @@ -29,6 +29,10 @@ import de.bluecolored.bluemap.common.web.http.HttpRequestHandler; import de.bluecolored.bluemap.common.web.http.HttpResponse; import de.bluecolored.bluemap.common.web.http.HttpStatusCode; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; import org.intellij.lang.annotations.Language; import java.util.LinkedList; @@ -38,7 +42,7 @@ @DebugDump public class RoutingRequestHandler implements HttpRequestHandler { - public LinkedList routes; + public final LinkedList routes; public RoutingRequestHandler() { this.routes = new LinkedList<>(); @@ -80,36 +84,20 @@ public HttpResponse handle(HttpRequest request) { } @DebugDump - private static class Route { + @AllArgsConstructor + @Getter @Setter + public static class Route { - private final Pattern routePattern; - private final HttpRequestHandler handler; - private final String replacementRoute; + private @NonNull Pattern routePattern; + private @NonNull String replacementRoute; + private @NonNull HttpRequestHandler handler; - public Route(Pattern routePattern, HttpRequestHandler handler) { + public Route(@NonNull Pattern routePattern, @NonNull HttpRequestHandler handler) { this.routePattern = routePattern; this.replacementRoute = "$0"; this.handler = handler; } - public Route(Pattern routePattern, String replacementRoute, HttpRequestHandler handler) { - this.routePattern = routePattern; - this.replacementRoute = replacementRoute; - this.handler = handler; - } - - public Pattern getRoutePattern() { - return routePattern; - } - - public HttpRequestHandler getHandler() { - return handler; - } - - public String getReplacementRoute() { - return replacementRoute; - } - } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java index 562d15f9..e11e7224 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java @@ -25,13 +25,16 @@ package de.bluecolored.bluemap.common.web.http; import de.bluecolored.bluemap.api.debug.DebugDump; +import lombok.Getter; +import lombok.Setter; import java.io.IOException; @DebugDump public class HttpServer extends Server { - private final HttpRequestHandler requestHandler; + @Getter @Setter + private HttpRequestHandler requestHandler; public HttpServer(HttpRequestHandler requestHandler) throws IOException { this.requestHandler = requestHandler; @@ -40,10 +43,6 @@ public HttpServer(HttpRequestHandler requestHandler) throws IOException { @Override public SelectionConsumer createConnectionHandler() { return new HttpConnection(requestHandler); - - // Enable async request handling ... - // TODO: maybe find a better/separate executor than using bluemap's common thread-pool - //return new HttpConnection(requestHandler, BlueMap.THREAD_POOL); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpStatusCode.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpStatusCode.java index 69407096..2787ee2e 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpStatusCode.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpStatusCode.java @@ -24,6 +24,9 @@ */ package de.bluecolored.bluemap.common.web.http; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor public enum HttpStatusCode { CONTINUE (100, "Continue"), @@ -47,13 +50,8 @@ public enum HttpStatusCode { SERVICE_UNAVAILABLE (503, "Service Unavailable"), HTTP_VERSION_NOT_SUPPORTED (505, "HTTP Version not supported"); - private int code; - private String message; - - private HttpStatusCode(int code, String message) { - this.code = code; - this.message = message; - } + private final int code; + private final String message; public int getCode(){ return code; diff --git a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf index b7605fa2..7745e2f7 100644 --- a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf +++ b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf @@ -5,7 +5,7 @@ # By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula), # you confirm that you own a license to Minecraft (Java Edition) -# and you agree that BlueMap will download and use a minecraft-client file (depending on the minecraft-version) from mojangs servers (https://launcher.mojang.com/) for you. +# and you agree that BlueMap will download and use a minecraft-client file (depending on the minecraft-version) from mojangs servers (https://piston-meta.mojang.com/) for you. # This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compliant with mojang's EULA. # BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.) # ${timestamp} diff --git a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf index 18aae94d..ca685b91 100644 --- a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf +++ b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/file.conf @@ -14,7 +14,9 @@ root: "${root}" # The compression-type that bluemap will use to compress generated map-data. # Available compression-types are: -# - GZIP -# - NONE -# The default is: GZIP +# - gzip +# - zstd +# - deflate +# - none +# The default is: gzip compression: gzip diff --git a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf index 807a0418..e8776244 100644 --- a/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf +++ b/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages/sql.conf @@ -40,6 +40,8 @@ max-connections: -1 # The compression-type that bluemap will use to compress generated map-data. # Available compression-types are: # - gzip +# - zstd +# - deflate # - none # The default is: gzip compression: gzip diff --git a/BlueMapCommon/webapp/public/sql.php b/BlueMapCommon/webapp/public/sql.php index 504b6434..6f338241 100644 --- a/BlueMapCommon/webapp/public/sql.php +++ b/BlueMapCommon/webapp/public/sql.php @@ -9,30 +9,27 @@ $username = 'root'; $password = ''; $database = 'bluemap'; -// set this to "none" if you disabled compression on your maps -$hiresCompression = 'gzip'; - // !!! END - DONT CHANGE ANYTHING AFTER THIS LINE !!! +// compression +$compressionHeaderMap = [ + "bluemap:none" => null, + "bluemap:gzip" => "gzip", + "bluemap:deflate" => "deflate", + "bluemap:zstd" => "zstd", + "bluemap:lz4" => "lz4" +] -// some helper functions -function error($code, $message = null) { - global $path; - - http_response_code($code); - header("Content-Type: text/plain"); - echo "BlueMap php-script - $code\n"; - if ($message != null) echo $message."\n"; - echo "Requested Path: $path"; - exit; -} - -function startsWith($haystack, $needle) { - return substr($haystack, 0, strlen($needle)) === $needle; -} +// meta files +$metaFileKeys = [ + "settings.json" => "bluemap:settings", + "textures.json" => "bluemap:textures", + "live/markers.json" => "bluemap:markers", + "live/players.json" => "bluemap:players", +] // mime-types for meta-files $mimeDefault = "application/octet-stream"; @@ -70,6 +67,34 @@ $mimeTypes = [ "woff2" => "font/woff2" ]; +// some helper functions +function error($code, $message = null) { + global $path; + + http_response_code($code); + header("Content-Type: text/plain"); + echo "BlueMap php-script - $code\n"; + if ($message != null) echo $message."\n"; + echo "Requested Path: $path"; + exit; +} + +function startsWith($haystack, $needle) { + return substr($haystack, 0, strlen($needle)) === $needle; +} + +function issetOrElse(& $var, $fallback) { + return isset($var) ? $var : $fallback; +} + +function compressionHeader($compressionKey) { + global $compressionHeaderMap; + + $compressionHeader = issetOrElse($compressionHeaderMap[$compressionKey], null); + if ($compressionHeader) + header("Content-Encoding: ".$compressionHeader); +} + function getMimeType($path) { global $mimeDefault, $mimeTypes; @@ -122,55 +147,55 @@ if (startsWith($path, "/maps/")) { // Initialize PDO try { - $sql = new PDO("$driver:host=$hostname;dbname=$database", $username, $password); + $sql = new PDO("$driver:host=$hostname;port=$port;dbname=$database", $username, $password); $sql->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $e ) { error(500, "Failed to connect to database"); } - // provide map-tiles if (startsWith($mapPath, "tiles/")) { // parse tile-coordinates preg_match_all("/tiles\/([\d\/]+)\/x(-?[\d\/]+)z(-?[\d\/]+).*/", $mapPath, $matches); $lod = intval($matches[1][0]); + $storage = $lod === 0 ? "bluemap:hires" : "bluemap:lowres/".$lod $tileX = intval(str_replace("/", "", $matches[2][0])); $tileZ = intval(str_replace("/", "", $matches[3][0])); - $compression = $lod === 0 ? $hiresCompression : "none"; // query for tile try { $statement = $sql->prepare(" - SELECT t.data - FROM bluemap_map_tile t + SELECT d.data, c.compression + FROM bluemap_grid_storage_data d INNER JOIN bluemap_map m - ON t.map = m.id - INNER JOIN bluemap_map_tile_compression c - ON t.compression = c.id + ON d.map = m.id + INNER JOIN bluemap_grid_storage s + ON d.storage = s.id + INNER JOIN bluemap_compression c + ON d.compression = c.id WHERE m.map_id = :map_id - AND t.lod = :lod - AND t.x = :x - AND t.z = :z - AND c.compression = :compression + AND s.key = :storage + AND d.x = :x + AND d.z = :z "); $statement->bindParam( ':map_id', $mapId, PDO::PARAM_STR ); - $statement->bindParam( ':lod', $lod, PDO::PARAM_INT ); + $statement->bindParam( ':storage', $storage, PDO::PARAM_STR ); $statement->bindParam( ':x', $tileX, PDO::PARAM_INT ); $statement->bindParam( ':z', $tileZ, PDO::PARAM_INT ); - $statement->bindParam( ':compression', $compression, PDO::PARAM_STR); + $statement->bindParam( ':compression', $compression, PDO::PARAM_STR ); $statement->setFetchMode(PDO::FETCH_ASSOC); $statement->execute(); // return result if ($line = $statement->fetch()) { header("Cache-Control: public,max-age=86400"); + compressionHeader($line["compression"]); - if ($compression !== "none") - header("Content-Encoding: $compression"); if ($lod === 0) { header("Content-Type: application/octet-stream"); } else { header("Content-Type: image/png"); } + send($line["data"]); exit; } @@ -183,27 +208,39 @@ if (startsWith($path, "/maps/")) { } // provide meta-files - try { - $statement = $sql->prepare(" - SELECT t.value - FROM bluemap_map_meta t - INNER JOIN bluemap_map m - ON t.map = m.id - WHERE m.map_id = :map_id - AND t.key = :map_path - "); - $statement->bindParam( ':map_id', $mapId, PDO::PARAM_STR ); - $statement->bindParam( ':map_path', $mapPath, PDO::PARAM_STR ); - $statement->setFetchMode(PDO::FETCH_ASSOC); - $statement->execute(); + $storage = issetOrElse($metaFileKeys[$mapPath], null); + if ($storage === null && startsWith($mapPath, "assets/")) + $storage = "bluemap:asset/".substr($mapPath, strlen("assets/")); - if ($line = $statement->fetch()) { - header("Cache-Control: public,max-age=86400"); - header("Content-Type: ".getMimeType($mapPath)); - send($line["value"]); - exit; - } - } catch (PDOException $e) { error(500, "Failed to fetch data"); } + if ($storage !== null) { + try { + $statement = $sql->prepare(" + SELECT d.data, c.compression + FROM bluemap_item_storage_data d + INNER JOIN bluemap_map m + ON d.map = m.id + INNER JOIN bluemap_item_storage s + ON d.storage = s.id + INNER JOIN bluemap_compression c + ON d.compression = c.id + WHERE m.map_id = :map_id + AND s.key = :storage + "); + $statement->bindParam( ':map_id', $mapId, PDO::PARAM_STR ); + $statement->bindParam( ':storage', $storage, PDO::PARAM_STR ); + $statement->setFetchMode(PDO::FETCH_ASSOC); + $statement->execute(); + + if ($line = $statement->fetch()) { + header("Cache-Control: public,max-age=86400"); + header("Content-Type: ".getMimeType($mapPath)); + compressionHeader($line["compression"]); + + send($line["data"]); + exit; + } + } catch (PDOException $e) { error(500, "Failed to fetch data"); } + } } diff --git a/BlueMapCore/build.gradle.kts b/BlueMapCore/build.gradle.kts index 8cf17304..1f5dca18 100644 --- a/BlueMapCore/build.gradle.kts +++ b/BlueMapCore/build.gradle.kts @@ -65,7 +65,7 @@ dependencies { api ("commons-io:commons-io:2.5") api ("org.spongepowered:configurate-hocon:4.1.2") api ("org.spongepowered:configurate-gson:4.1.2") - api ("de.bluecolored.bluenbt:BlueNBT:2.2.1") + api ("de.bluecolored.bluenbt:BlueNBT:2.3.0") api ("org.apache.commons:commons-dbcp2:2.9.0") api ("io.airlift:aircompressor:0.24") api ("org.lz4:lz4-java:1.8.0") diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/MinecraftVersion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/MinecraftVersion.java deleted file mode 100644 index 876a2cab..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/MinecraftVersion.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core; - -import de.bluecolored.bluemap.api.debug.DebugDump; -import de.bluecolored.bluemap.core.util.Lazy; - -import java.util.Arrays; -import java.util.Comparator; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -@DebugDump -public class MinecraftVersion implements Comparable { - - private static final Pattern VERSION_REGEX = Pattern.compile("(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?(?:-(?:pre|rc)\\d+)?"); - - public static final MinecraftVersion LATEST_SUPPORTED = new MinecraftVersion(1, 20, 3); - public static final MinecraftVersion EARLIEST_SUPPORTED = new MinecraftVersion(1, 13); - - private final int major, minor, patch; - - private final Lazy resource; - - public MinecraftVersion(int major, int minor) { - this(major, minor, 0); - } - - public MinecraftVersion(int major, int minor, int patch) { - this.major = major; - this.minor = minor; - this.patch = patch; - - this.resource = new Lazy<>(this::findBestMatchingResource); - } - - public String getVersionString() { - return major + "." + minor + "." + patch; - } - - public MinecraftResource getResource() { - return this.resource.getValue(); - } - - public boolean isAtLeast(MinecraftVersion minVersion) { - return compareTo(minVersion) >= 0; - } - - public boolean isAtMost(MinecraftVersion maxVersion) { - return compareTo(maxVersion) <= 0; - } - - public boolean isBefore(MinecraftVersion minVersion) { - return compareTo(minVersion) < 0; - } - - public boolean isAfter(MinecraftVersion minVersion) { - return compareTo(minVersion) > 0; - } - - @Override - public int compareTo(MinecraftVersion other) { - int result; - - result = Integer.compare(major, other.major); - if (result != 0) return result; - - result = Integer.compare(minor, other.minor); - if (result != 0) return result; - - result = Integer.compare(patch, other.patch); - return result; - } - - public boolean majorEquals(MinecraftVersion that) { - return major == that.major; - } - - public boolean minorEquals(MinecraftVersion that) { - return major == that.major && minor == that.minor; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MinecraftVersion that = (MinecraftVersion) o; - return major == that.major && minor == that.minor && patch == that.patch; - } - - @Override - public int hashCode() { - return Objects.hash(major, minor, patch); - } - - private MinecraftResource findBestMatchingResource() { - MinecraftResource[] resources = MinecraftResource.values(); - Arrays.sort(resources, Comparator.comparing(MinecraftResource::getVersion).reversed()); - - for (MinecraftResource resource : resources){ - if (isAtLeast(resource.version)) return resource; - } - - return resources[resources.length - 1]; - } - - public static MinecraftVersion of(String versionString) { - Matcher matcher = VERSION_REGEX.matcher(versionString); - if (!matcher.matches()) throw new IllegalArgumentException("Not a valid version string!"); - - int major = Integer.parseInt(matcher.group("major")); - int minor = Integer.parseInt(matcher.group("minor")); - int patch = 0; - String patchString = matcher.group("patch"); - if (patchString != null) patch = Integer.parseInt(patchString); - - return new MinecraftVersion(major, minor, patch); - } - - @DebugDump - public enum MinecraftResource { - - MC_1_13 (new MinecraftVersion(1, 13), "mc1_13", "https://piston-data.mojang.com/v1/objects/30bfe37a8db404db11c7edf02cb5165817afb4d9/client.jar"), - MC_1_14 (new MinecraftVersion(1, 14), "mc1_13", "https://piston-data.mojang.com/v1/objects/8c325a0c5bd674dd747d6ebaa4c791fd363ad8a9/client.jar"), - MC_1_15 (new MinecraftVersion(1, 15), "mc1_15", "https://piston-data.mojang.com/v1/objects/e3f78cd16f9eb9a52307ed96ebec64241cc5b32d/client.jar"), - MC_1_16 (new MinecraftVersion(1, 16), "mc1_16", "https://piston-data.mojang.com/v1/objects/228fdf45541c4c2fe8aec4f20e880cb8fcd46621/client.jar"), - MC_1_16_2 (new MinecraftVersion(1, 16, 2), "mc1_16", "https://piston-data.mojang.com/v1/objects/653e97a2d1d76f87653f02242d243cdee48a5144/client.jar"), - MC_1_17 (new MinecraftVersion(1, 17), "mc1_16", "https://piston-data.mojang.com/v1/objects/1cf89c77ed5e72401b869f66410934804f3d6f52/client.jar"), - MC_1_18 (new MinecraftVersion(1, 18), "mc1_18", "https://piston-data.mojang.com/v1/objects/020aa79e63a7aab5d6f30e5ec7a6c08baee6b64c/client.jar"), - MC_1_19 (new MinecraftVersion(1, 19), "mc1_18", "https://piston-data.mojang.com/v1/objects/a45634ab061beb8c878ccbe4a59c3315f9c0266f/client.jar"), - MC_1_19_4 (new MinecraftVersion(1, 19, 4), "mc1_18", "https://piston-data.mojang.com/v1/objects/958928a560c9167687bea0cefeb7375da1e552a8/client.jar"), - MC_1_20 (new MinecraftVersion(1, 20), "mc1_18", "https://piston-data.mojang.com/v1/objects/e575a48efda46cf88111ba05b624ef90c520eef1/client.jar"), - MC_1_20_3 (new MinecraftVersion(1, 20, 3), "mc1_20_3", "https://piston-data.mojang.com/v1/objects/b178a327a96f2cf1c9f98a45e5588d654a3e4369/client.jar"); - - private final MinecraftVersion version; - private final String resourcePrefix; - private final String clientUrl; - - MinecraftResource(MinecraftVersion version, String resourcePrefix, String clientUrl) { - this.version = version; - this.resourcePrefix = resourcePrefix; - this.clientUrl = clientUrl; - } - - public MinecraftVersion getVersion() { - return version; - } - - public String getResourcePrefix() { - return resourcePrefix; - } - - public String getClientUrl() { - return clientUrl; - } - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java index c1ac4000..01851a38 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java @@ -34,21 +34,29 @@ import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.hires.HiresModelManager; import de.bluecolored.bluemap.core.map.lowres.LowresTileManager; +import de.bluecolored.bluemap.core.map.renderstate.MapChunkState; +import de.bluecolored.bluemap.core.map.renderstate.MapTileState; import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.storage.MapStorage; import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream; import de.bluecolored.bluemap.core.util.Grid; import de.bluecolored.bluemap.core.world.World; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; -import java.io.*; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.nio.charset.StandardCharsets; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; @DebugDump +@Getter public class BmMap { private static final Gson GSON = ResourcesGson.addAdapter(new GsonBuilder()) @@ -63,20 +71,23 @@ public class BmMap { private final MapSettings mapSettings; private final ResourcePack resourcePack; - private final MapRenderState renderState; private final TextureGallery textureGallery; + private final MapTileState mapTileState; + private final MapChunkState mapChunkState; + private final HiresModelManager hiresModelManager; private final LowresTileManager lowresTileManager; private final ConcurrentHashMap markerSets; - private Predicate tileFilter; + @Setter private Predicate tileFilter; - private long renderTimeSumNanos; - private long tilesRendered; + @Getter(AccessLevel.NONE) private long renderTimeSumNanos; + @Getter(AccessLevel.NONE) private long tilesRendered; + @Getter(AccessLevel.NONE) private long lastSaveTime; - public BmMap(String id, String name, World world, MapStorage storage, ResourcePack resourcePack, MapSettings settings) throws IOException { + public BmMap(String id, String name, World world, MapStorage storage, ResourcePack resourcePack, MapSettings settings) throws IOException, InterruptedException { this.id = Objects.requireNonNull(id); this.name = Objects.requireNonNull(name); this.world = Objects.requireNonNull(world); @@ -84,9 +95,14 @@ public BmMap(String id, String name, World world, MapStorage storage, ResourcePa this.resourcePack = Objects.requireNonNull(resourcePack); this.mapSettings = Objects.requireNonNull(settings); - this.renderState = new MapRenderState(); - loadRenderState(); + Logger.global.logDebug("Loading render-state for map '" + id + "'"); + this.mapTileState = new MapTileState(storage.tileState()); + this.mapTileState.load(); + this.mapChunkState = new MapChunkState(storage.chunkState()); + if (Thread.interrupted()) throw new InterruptedException(); + + Logger.global.logDebug("Loading textures for map '" + id + "'"); this.textureGallery = loadTextureGallery(); this.textureGallery.put(resourcePack); saveTextureGallery(); @@ -112,6 +128,7 @@ public BmMap(String id, String name, World world, MapStorage storage, ResourcePa this.renderTimeSumNanos = 0; this.tilesRendered = 0; + this.lastSaveTime = -1; saveMapSettings(); } @@ -130,9 +147,23 @@ public void renderTile(Vector2i tile) { tilesRendered ++; } + public void unrenderTile(Vector2i tile) { + hiresModelManager.unrender(tile, lowresTileManager); + } + + public synchronized boolean save(long minTimeSinceLastSave) { + long now = System.currentTimeMillis(); + if (now - lastSaveTime < minTimeSinceLastSave) + return false; + + save(); + return true; + } + public synchronized void save() { lowresTileManager.save(); - saveRenderState(); + mapTileState.save(); + mapChunkState.save(); saveMarkerState(); savePlayerState(); saveMapSettings(); @@ -142,25 +173,10 @@ public synchronized void save() { if (!storage.textures().exists()) saveTextureGallery(); } catch (IOException e) { - Logger.global.logError("Failed to read texture gallery", e); + Logger.global.logError("Failed to read texture gallery for map '" + getId() + "'!", e); } - } - private void loadRenderState() throws IOException { - try (CompressedInputStream in = storage.renderState().read()){ - if (in != null) - this.renderState.load(in.decompress()); - } catch (IOException ex) { - Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex); - } - } - - public synchronized void saveRenderState() { - try (OutputStream out = storage.renderState().write()) { - this.renderState.save(out); - } catch (IOException ex){ - Logger.global.logError("Failed to save render-state for map: '" + this.id + "'!", ex); - } + lastSaveTime = System.currentTimeMillis(); } private TextureGallery loadTextureGallery() throws IOException { @@ -217,50 +233,6 @@ public synchronized void savePlayerState() { } } - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public World getWorld() { - return world; - } - - public MapStorage getStorage() { - return storage; - } - - public MapSettings getMapSettings() { - return mapSettings; - } - - public MapRenderState getRenderState() { - return renderState; - } - - public HiresModelManager getHiresModelManager() { - return hiresModelManager; - } - - public LowresTileManager getLowresTileManager() { - return lowresTileManager; - } - - public Map getMarkerSets() { - return markerSets; - } - - public Predicate getTileFilter() { - return tileFilter; - } - - public void setTileFilter(Predicate tileFilter) { - this.tileFilter = tileFilter; - } - public long getAverageNanosPerTile() { return renderTimeSumNanos / tilesRendered; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java deleted file mode 100644 index 5adeabdf..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.map; - -import com.flowpowered.math.vector.Vector2i; -import de.bluecolored.bluemap.api.debug.DebugDump; - -import java.io.*; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -@DebugDump -public class MapRenderState { - - private final Map regionRenderTimes; - private transient long latestRenderTime = -1; - - public MapRenderState() { - regionRenderTimes = new HashMap<>(); - } - - public synchronized void setRenderTime(Vector2i regionPos, long renderTime) { - regionRenderTimes.put(regionPos, renderTime); - - if (latestRenderTime != -1) { - if (renderTime > latestRenderTime) - latestRenderTime = renderTime; - else - latestRenderTime = -1; - } - } - - public synchronized long getRenderTime(Vector2i regionPos) { - Long renderTime = regionRenderTimes.get(regionPos); - if (renderTime == null) return -1; - else return renderTime; - } - - public long getLatestRenderTime() { - if (latestRenderTime == -1) { - synchronized (this) { - latestRenderTime = regionRenderTimes.values().stream() - .mapToLong(Long::longValue) - .max() - .orElse(-1); - } - } - - return latestRenderTime; - } - - public synchronized void reset() { - regionRenderTimes.clear(); - } - - public synchronized void save(OutputStream out) throws IOException { - try ( - DataOutputStream dOut = new DataOutputStream(new GZIPOutputStream(out)) - ) { - dOut.writeInt(regionRenderTimes.size()); - - for (Map.Entry entry : regionRenderTimes.entrySet()) { - Vector2i regionPos = entry.getKey(); - long renderTime = entry.getValue(); - - dOut.writeInt(regionPos.getX()); - dOut.writeInt(regionPos.getY()); - dOut.writeLong(renderTime); - } - - dOut.flush(); - } - } - - public synchronized void load(InputStream in) throws IOException { - regionRenderTimes.clear(); - - try ( - DataInputStream dIn = new DataInputStream(new GZIPInputStream(in)) - ) { - int size = dIn.readInt(); - - for (int i = 0; i < size; i++) { - Vector2i regionPos = new Vector2i( - dIn.readInt(), - dIn.readInt() - ); - long renderTime = dIn.readLong(); - - regionRenderTimes.put(regionPos, renderTime); - } - } catch (EOFException ignore){} // ignoring a sudden end of stream, since it is save to only read as many as we can - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java index 75f7f9d8..2a5c8977 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/TextureGallery.java @@ -32,8 +32,8 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.resources.ResourcePath; import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.Texture; import de.bluecolored.bluemap.core.util.Key; import org.jetbrains.annotations.Nullable; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java index 89e095e9..d2ebdfdb 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java @@ -29,9 +29,10 @@ import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.TextureGallery; import de.bluecolored.bluemap.core.map.TileMetaConsumer; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.storage.GridStorage; import de.bluecolored.bluemap.core.util.Grid; +import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.world.World; import lombok.Getter; @@ -79,6 +80,23 @@ public void render(World world, Vector2i tile, TileMetaConsumer tileMetaConsumer TileModel.instancePool().recycleInstance(model); } + /** + * Un-renders a tile. + * The hires tile is deleted and the tileMetaConsumer (lowres) is updated with default values in the tiles area. + */ + public void unrender(Vector2i tile, TileMetaConsumer tileMetaConsumer) { + try { + storage.delete(tile.getX(), tile.getY()); + } catch (IOException ex) { + Logger.global.logError("Failed to delete hires model: " + tile, ex); + } + + Color color = new Color(); + tileGrid.forEachIntersecting(tile, Grid.UNIT, (x, z) -> + tileMetaConsumer.set(x, z, color, 0, 0) + ); + } + private void save(final TileModel model, Vector2i tile) { try ( OutputStream out = storage.write(tile.getX(), tile.getY()); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java index 522e7c6b..e88c114b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java @@ -28,7 +28,7 @@ import de.bluecolored.bluemap.core.map.TextureGallery; import de.bluecolored.bluemap.core.map.TileMetaConsumer; import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.block.BlockNeighborhood; @@ -65,8 +65,8 @@ public void render(World world, Vector3i modelMin, Vector3i modelMax, TileModel BlockModelView blockModel = new BlockModelView(model); int x, y, z; - for (x = min.getX(); x <= max.getX(); x++){ - for (z = min.getZ(); z <= max.getZ(); z++){ + for (x = modelMin.getX(); x <= modelMax.getX(); x++){ + for (z = modelMin.getZ(); z <= modelMax.getZ(); z++){ maxHeight = 0; topBlockLight = 0; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java index 6d85cb17..ff97d115 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java @@ -105,20 +105,22 @@ default boolean isInsideRenderBoundaries(int x, int y, int z) { y <= max.getY(); } + default boolean isInsideRenderBoundaries(Vector2i cell, Grid grid, boolean allowPartiallyIncludedCells) { + Vector2i cellMin = allowPartiallyIncludedCells ? grid.getCellMin(cell) : grid.getCellMax(cell); + if (cellMin.getX() > getMaxPos().getX()) return false; + if (cellMin.getY() > getMaxPos().getZ()) return false; + + Vector2i cellMax = allowPartiallyIncludedCells ? grid.getCellMax(cell) : grid.getCellMin(cell); + if (cellMax.getX() < getMinPos().getX()) return false; + return cellMax.getY() >= getMinPos().getZ(); + } + /** * Returns a predicate which is filtering out all cells of a {@link Grid} - * that are completely outside the render boundaries. + * that are outside the render boundaries. */ - default Predicate getRenderBoundariesCellFilter(Grid grid) { - return t -> { - Vector2i cellMin = grid.getCellMin(t); - if (cellMin.getX() > getMaxPos().getX()) return false; - if (cellMin.getY() > getMaxPos().getZ()) return false; - - Vector2i cellMax = grid.getCellMax(t); - if (cellMax.getX() < getMinPos().getX()) return false; - return cellMax.getY() >= getMinPos().getZ(); - }; + default Predicate getCellRenderBoundariesFilter(Grid grid, boolean allowPartiallyIncludedCells) { + return cell -> isInsideRenderBoundaries(cell, grid, allowPartiallyIncludedCells); } boolean isSaveHiresLayer(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java index 3539043c..c3a2af9b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java @@ -27,9 +27,9 @@ import de.bluecolored.bluemap.core.map.TextureGallery; import de.bluecolored.bluemap.core.map.hires.BlockModelView; import de.bluecolored.bluemap.core.map.hires.RenderSettings; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel; -import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.BlockModel; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate.Variant; import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.world.block.BlockNeighborhood; import de.bluecolored.bluemap.core.world.BlockState; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java index 1b915f36..f4b6d00c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java @@ -32,11 +32,11 @@ import de.bluecolored.bluemap.core.map.hires.RenderSettings; import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory; import de.bluecolored.bluemap.core.resources.ResourcePath; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.TextureVariable; -import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant; -import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.BlockModel; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.TextureVariable; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate.Variant; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.Texture; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.util.math.MatrixM3f; @@ -138,7 +138,7 @@ private void build() { tintcolor.set(1f, 1f, 1f, 1f, true); if (blockState.isWater()) { - blockColorCalculator.getWaterAverageColor(block, tintcolor); + blockColorCalculator.getBlendedWaterColor(block, tintcolor); } int modelStart = blockModel.getStart(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java index 686eb6ca..3e709484 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java @@ -34,12 +34,12 @@ import de.bluecolored.bluemap.core.map.hires.RenderSettings; import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory; import de.bluecolored.bluemap.core.resources.ResourcePath; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Element; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face; -import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant; -import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.BlockModel; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.Element; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.Face; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate.Variant; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.Texture; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.util.math.MatrixM4f; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/CellStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/CellStorage.java new file mode 100644 index 00000000..f133d88b --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/CellStorage.java @@ -0,0 +1,116 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.map.renderstate; + +import com.flowpowered.math.vector.Vector2i; +import com.google.gson.reflect.TypeToken; +import de.bluecolored.bluemap.api.debug.DebugDump; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.storage.GridStorage; +import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream; +import de.bluecolored.bluemap.core.util.Key; +import de.bluecolored.bluemap.core.util.PalettedArrayAdapter; +import de.bluecolored.bluemap.core.util.RegistryAdapter; +import de.bluecolored.bluenbt.BlueNBT; +import lombok.Getter; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.LinkedHashMap; +import java.util.Map; + +@DebugDump +abstract class CellStorage { + + private static final BlueNBT BLUE_NBT = new BlueNBT(); + static { + BLUE_NBT.register(TypeToken.get(TileState.class), new RegistryAdapter<>(TileState.REGISTRY, Key.BLUEMAP_NAMESPACE, TileState.UNKNOWN)); + BLUE_NBT.register(TypeToken.get(TileState[].class), new PalettedArrayAdapter<>(BLUE_NBT, TileState.class)); + } + + private static final int CACHE_SIZE = 4; + + @Getter private final GridStorage storage; + private final Class type; + private final LinkedHashMap cells = new LinkedHashMap<>( + 8, + 0.75f, + true + ) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + if (this.size() <= CACHE_SIZE) return false; + saveCell(eldest.getKey(), eldest.getValue()); + return true; + } + }; + + public CellStorage(GridStorage storage, Class type) { + this.storage = storage; + this.type = type; + } + + public synchronized void save() { + cells.forEach(this::saveCell); + } + + public synchronized void reset() { + cells.clear(); + } + + T cell(int x, int z) { + return cell(new Vector2i(x, z)); + } + + synchronized T cell(Vector2i pos) { + return cells.computeIfAbsent(pos, this::loadCell); + } + + private synchronized T loadCell(Vector2i pos) { + try (CompressedInputStream in = storage.read(pos.getX(), pos.getY())) { + if (in != null) + return BLUE_NBT.read(in.decompress(), type); + } catch (IOException ex) { + Logger.global.logError("Failed to load render-state cell " + pos, ex); + } + return createNewCell(); + } + + protected abstract T createNewCell(); + + private synchronized void saveCell(Vector2i pos, T cell) { + if (!cell.isModified()) return; + try (OutputStream in = storage.write(pos.getX(), pos.getY())) { + BLUE_NBT.write(cell, in, type); + } catch (IOException ex) { + Logger.global.logError("Failed to save render-state cell " + pos, ex); + } + } + + public interface Cell { + boolean isModified(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/ChunkInfoRegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/ChunkInfoRegion.java new file mode 100644 index 00000000..29e7544d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/ChunkInfoRegion.java @@ -0,0 +1,55 @@ +package de.bluecolored.bluemap.core.map.renderstate; + +import de.bluecolored.bluenbt.NBTName; +import de.bluecolored.bluenbt.NBTPostDeserialize; +import lombok.Getter; + +import static de.bluecolored.bluemap.core.map.renderstate.MapChunkState.SHIFT; + +public class ChunkInfoRegion implements CellStorage.Cell { + + static final int REGION_LENGTH = 1 << SHIFT; + static final int REGION_MASK = REGION_LENGTH - 1; + static final int CHUNKS_PER_REGION = REGION_LENGTH * REGION_LENGTH; + + @NBTName("chunk-hashes") + private int[] chunkHashes; + + @Getter + private transient boolean modified; + + private ChunkInfoRegion() {} + + @NBTPostDeserialize + public void init() { + if (chunkHashes == null || chunkHashes.length != CHUNKS_PER_REGION) + chunkHashes = new int[CHUNKS_PER_REGION]; + } + + public int get(int x, int z) { + return chunkHashes[index(x, z)]; + } + + public int set(int x, int z, int hash) { + int index = index(x, z); + int previous = chunkHashes[index]; + + chunkHashes[index] = hash; + + if (previous != hash) + modified = true; + + return previous; + } + + private static int index(int x, int z) { + return (z & REGION_MASK) << SHIFT | (x & REGION_MASK); + } + + public static ChunkInfoRegion create() { + ChunkInfoRegion region = new ChunkInfoRegion(); + region.init(); + return region; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiome.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/MapChunkState.java similarity index 64% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiome.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/MapChunkState.java index 601b0700..945a888c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiome.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/MapChunkState.java @@ -22,28 +22,31 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.biome.datapack; +package de.bluecolored.bluemap.core.map.renderstate; -import de.bluecolored.bluemap.core.world.Biome; -import lombok.Getter; +import de.bluecolored.bluemap.api.debug.DebugDump; +import de.bluecolored.bluemap.core.storage.GridStorage; -@SuppressWarnings("FieldMayBeFinal") -@Getter -public class DpBiome { +@DebugDump +public class MapChunkState extends CellStorage { - private DpBiomeEffects effects = new DpBiomeEffects(); - private float temperature = Biome.DEFAULT.getTemp(); - private float downfall = Biome.DEFAULT.getHumidity(); + static final int SHIFT = 7; - public Biome createBiome(String formatted) { - return new Biome( - formatted, - downfall, - temperature, - effects.getWaterColor(), - effects.getFoliageColor(), - effects.getGrassColor() - ); + public MapChunkState(GridStorage storage) { + super(storage, ChunkInfoRegion.class); + } + + public int get(int x, int z) { + return cell(x >> SHIFT, z >> SHIFT).get(x, z); + } + + public synchronized int set(int x, int z, int hash) { + return cell(x >> SHIFT, z >> SHIFT).set(x, z, hash); + } + + @Override + protected ChunkInfoRegion createNewCell() { + return ChunkInfoRegion.create(); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/MapTileState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/MapTileState.java new file mode 100644 index 00000000..f3796698 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/MapTileState.java @@ -0,0 +1,101 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.map.renderstate; + +import de.bluecolored.bluemap.api.debug.DebugDump; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.storage.GridStorage; +import de.bluecolored.bluemap.core.util.Grid; +import lombok.Getter; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +@DebugDump +public class MapTileState extends CellStorage { + + static final int SHIFT = 5; + public static final Grid GRID = new Grid(1 << SHIFT); + + @Getter private int lastRenderTime = -1; + private final Map chunkStateCounts = new ConcurrentHashMap<>(); + + public MapTileState(GridStorage storage) { + super(storage, TileInfoRegion.class); + } + + public synchronized void load() { + lastRenderTime = -1; + chunkStateCounts.clear(); + + try (Stream stream = getStorage().stream()) { + stream.forEach(cell -> { + TileInfoRegion region = cell(cell.getX(), cell.getZ()); + lastRenderTime = Math.max(lastRenderTime, region.findLatestRenderTime()); + region.populateSummaryMap(chunkStateCounts); + }); + } catch (IOException e) { + Logger.global.logError("Failed to load render-state regions", e); + } + } + + public synchronized void reset() { + super.reset(); + load(); + } + + public TileInfoRegion.TileInfo get(int x, int z) { + return cell(x >> SHIFT, z >> SHIFT).get(x, z); + } + + public synchronized TileInfoRegion.TileInfo set(int x, int z, TileInfoRegion.TileInfo info) { + TileInfoRegion.TileInfo old = cell(x >> SHIFT, z >> SHIFT).set(x, z, info); + + if (info.getRenderTime() > lastRenderTime) + lastRenderTime = info.getRenderTime(); + + if (old.getState() != info.getState()) { + chunkStateCounts.merge(old.getState(), -1, Integer::sum); + chunkStateCounts.merge(info.getState(), 1, Integer::sum); + } + + return old; + } + + public Map getChunkStateCounts() { + return Collections.unmodifiableMap(chunkStateCounts); + } + + @Override + protected synchronized TileInfoRegion createNewCell() { + TileInfoRegion region = TileInfoRegion.create(); + region.populateSummaryMap(chunkStateCounts); + return region; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileActionResolver.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileActionResolver.java new file mode 100644 index 00000000..3816e3cb --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileActionResolver.java @@ -0,0 +1,42 @@ +package de.bluecolored.bluemap.core.map.renderstate; + +import java.util.Objects; + +import static de.bluecolored.bluemap.core.map.renderstate.TileState.*; + +@FunctionalInterface +public interface TileActionResolver { + + ActionAndNextState findActionAndNextState( + boolean chunksChanged, + BoundsSituation bounds + ); + + enum BoundsSituation { + INSIDE, + EDGE, + OUTSIDE + } + + enum Action { + NONE, + RENDER, + DELETE + } + + record ActionAndNextState (Action action, TileState state) { + + public ActionAndNextState(Action action, TileState state) { + this.action = Objects.requireNonNull(action); + this.state = Objects.requireNonNull(state); + } + + public static final ActionAndNextState RENDER_RENDERED = new ActionAndNextState(Action.RENDER, RENDERED); + public static final ActionAndNextState NONE_RENDERED = new ActionAndNextState(Action.NONE, RENDERED); + public static final ActionAndNextState RENDER_RENDERED_EDGE = new ActionAndNextState(Action.RENDER, RENDERED_EDGE); + public static final ActionAndNextState NONE_RENDERED_EDGE = new ActionAndNextState(Action.NONE, RENDERED_EDGE); + public static final ActionAndNextState DELETE_OUT_OF_BOUNDS = new ActionAndNextState(Action.DELETE, OUT_OF_BOUNDS); + public static final ActionAndNextState NONE_OUT_OF_BOUNDS = new ActionAndNextState(Action.NONE, OUT_OF_BOUNDS); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileInfoRegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileInfoRegion.java new file mode 100644 index 00000000..600b6fb0 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileInfoRegion.java @@ -0,0 +1,101 @@ +package de.bluecolored.bluemap.core.map.renderstate; + +import de.bluecolored.bluenbt.NBTName; +import de.bluecolored.bluenbt.NBTPostDeserialize; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +import static de.bluecolored.bluemap.core.map.renderstate.MapTileState.SHIFT; + +public class TileInfoRegion implements CellStorage.Cell { + + private static final int REGION_LENGTH = 1 << SHIFT; + private static final int REGION_MASK = REGION_LENGTH - 1; + private static final int TILES_PER_REGION = REGION_LENGTH * REGION_LENGTH; + + @NBTName("last-render-times") + private int[] lastRenderTimes; + + @NBTName("tile-states") + private TileState[] tileStates; + + @Getter + private transient boolean modified; + + private TileInfoRegion() {} + + @NBTPostDeserialize + public void init() { + if (lastRenderTimes == null || lastRenderTimes.length != TILES_PER_REGION) + lastRenderTimes = new int[TILES_PER_REGION]; + + if (tileStates == null || tileStates.length != TILES_PER_REGION) { + tileStates = new TileState[TILES_PER_REGION]; + Arrays.fill(tileStates, TileState.UNKNOWN); + } + } + + public TileInfo get(int x, int z) { + int index = index(x, z); + return new TileInfo( + lastRenderTimes[index], + tileStates[index] + ); + } + + public TileInfo set(int x, int z, TileInfo info) { + int index = index(x, z); + + TileInfo previous = new TileInfo( + lastRenderTimes[index], + tileStates[index] + ); + + lastRenderTimes[index] = info.getRenderTime(); + tileStates[index] = Objects.requireNonNull(info.getState()); + + if (!previous.equals(info)) + this.modified = true; + + return previous; + } + + int findLatestRenderTime() { + if (lastRenderTimes == null) return -1; + return Arrays.stream(lastRenderTimes) + .max() + .orElse(-1); + } + + void populateSummaryMap(Map map) { + for (int i = 0; i < TILES_PER_REGION; i++) { + TileState tileState = tileStates[i]; + map.merge(tileState, 1, Integer::sum); + } + } + + private static int index(int x, int z) { + return (z & REGION_MASK) << SHIFT | (x & REGION_MASK); + } + + @Data + @AllArgsConstructor + public static class TileInfo { + + private int renderTime; + private TileState state; + + } + + public static TileInfoRegion create() { + TileInfoRegion region = new TileInfoRegion(); + region.init(); + return region; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileState.java new file mode 100644 index 00000000..e870016f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/renderstate/TileState.java @@ -0,0 +1,102 @@ +package de.bluecolored.bluemap.core.map.renderstate; + +import de.bluecolored.bluemap.core.util.Key; +import de.bluecolored.bluemap.core.util.Keyed; +import de.bluecolored.bluemap.core.util.Registry; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import static de.bluecolored.bluemap.core.map.renderstate.TileActionResolver.ActionAndNextState.*; + +public interface TileState extends Keyed, TileActionResolver { + + TileState UNKNOWN = new Impl( Key.bluemap("unknown")); + + TileState RENDERED = new Impl(Key.bluemap("rendered"), (changed, bounds) -> + switch (bounds) { + case INSIDE -> changed ? RENDER_RENDERED : NONE_RENDERED; + case EDGE -> RENDER_RENDERED_EDGE; + case OUTSIDE -> DELETE_OUT_OF_BOUNDS; + } + ); + TileState RENDERED_EDGE = new Impl(Key.bluemap("rendered-edge"), (changed, bounds) -> + switch (bounds) { + case INSIDE -> RENDER_RENDERED; + case EDGE -> changed ? RENDER_RENDERED_EDGE : NONE_RENDERED_EDGE; + case OUTSIDE -> DELETE_OUT_OF_BOUNDS; + } + ); + TileState OUT_OF_BOUNDS = new Impl(Key.bluemap("out-of-bounds"), (changed, bounds) -> + switch (bounds) { + case INSIDE -> RENDER_RENDERED; + case EDGE -> RENDER_RENDERED_EDGE; + case OUTSIDE -> NONE_OUT_OF_BOUNDS; + } + ); + + TileState NOT_GENERATED = new Impl(Key.bluemap("not-generated")); + TileState MISSING_LIGHT = new Impl(Key.bluemap("missing-light")); + TileState LOW_INHABITED_TIME = new Impl(Key.bluemap("low-inhabited-time")); + TileState CHUNK_ERROR = new Impl(Key.bluemap("chunk-error")); + + TileState RENDER_ERROR = new Impl(Key.bluemap("render-error"), (changed, bounds) -> + switch (bounds) { + case INSIDE -> RENDER_RENDERED; + case EDGE -> RENDER_RENDERED_EDGE; + case OUTSIDE -> DELETE_OUT_OF_BOUNDS; + } + ); + + Registry REGISTRY = new Registry<>( + UNKNOWN, + RENDERED, + RENDERED_EDGE, + OUT_OF_BOUNDS, + NOT_GENERATED, + MISSING_LIGHT, + LOW_INHABITED_TIME, + CHUNK_ERROR, + RENDER_ERROR + ); + + @Getter + @RequiredArgsConstructor + class Impl implements TileState { + private final Key key; + private final TileActionResolver resolver; + + public Impl(Key key) { + this.key = key; + this.resolver = (changed, bounds) -> { + if (!changed) return noActionThisNextState(); + return switch (bounds) { + case INSIDE -> RENDER_RENDERED; + case EDGE -> RENDER_RENDERED_EDGE; + case OUTSIDE -> DELETE_OUT_OF_BOUNDS; + }; + }; + } + + @Override + public String toString() { + return key.getFormatted(); + } + + @Override + public ActionAndNextState findActionAndNextState( + boolean changed, + BoundsSituation bounds + ) { + return resolver.findActionAndNextState(changed, bounds); + } + + private ActionAndNextState noActionThisNextState; + private ActionAndNextState noActionThisNextState() { + if (noActionThisNextState == null) + noActionThisNextState = new ActionAndNextState(Action.NONE, this); + return noActionThisNextState; + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java index e745c5e7..9b8ebad9 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/BlockColorCalculatorFactory.java @@ -29,6 +29,7 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.world.Biome; +import de.bluecolored.bluemap.core.world.block.Block; import de.bluecolored.bluemap.core.world.block.BlockNeighborhood; import java.awt.image.BufferedImage; @@ -42,13 +43,15 @@ @DebugDump public class BlockColorCalculatorFactory { + private static final int BLEND_RADIUS_H = 2; + private static final int BLEND_RADIUS_V = 1; private static final int - AVERAGE_MIN_X = - 2, - AVERAGE_MAX_X = 2, - AVERAGE_MIN_Y = - 1, - AVERAGE_MAX_Y = 1, - AVERAGE_MIN_Z = - 2, - AVERAGE_MAX_Z = 2; + BLEND_MIN_X = - BLEND_RADIUS_H, + BLEND_MAX_X = BLEND_RADIUS_H, + BLEND_MIN_Y = - BLEND_RADIUS_V, + BLEND_MAX_Y = BLEND_RADIUS_V, + BLEND_MIN_Z = - BLEND_RADIUS_H, + BLEND_MAX_Z = BLEND_RADIUS_H; private final int[] foliageMap = new int[65536]; private final int[] grassMap = new int[65536]; @@ -73,13 +76,13 @@ public void load(Path configFile) throws IOException { ColorFunction colorFunction; switch (value) { case "@foliage": - colorFunction = BlockColorCalculator::getFoliageAverageColor; + colorFunction = BlockColorCalculator::getBlendedFoliageColor; break; case "@grass": - colorFunction = BlockColorCalculator::getGrassAverageColor; + colorFunction = BlockColorCalculator::getBlendedGrassColor; break; case "@water": - colorFunction = BlockColorCalculator::getWaterAverageColor; + colorFunction = BlockColorCalculator::getBlendedWaterColor; break; case "@redstone": colorFunction = BlockColorCalculator::getRedstoneColor; @@ -120,17 +123,18 @@ public class BlockColorCalculator { private final Color tempColor = new Color(); + @SuppressWarnings("UnusedReturnValue") public Color getBlockColor(BlockNeighborhood block, Color target) { String blockId = block.getBlockState().getFormatted(); ColorFunction colorFunction = blockColorMap.get(blockId); if (colorFunction == null) colorFunction = blockColorMap.get("default"); - if (colorFunction == null) colorFunction = BlockColorCalculator::getFoliageAverageColor; + if (colorFunction == null) colorFunction = BlockColorCalculator::getBlendedFoliageColor; return colorFunction.invoke(this, block, target); } - public Color getRedstoneColor(BlockNeighborhood block, Color target) { + public Color getRedstoneColor(Block block, Color target) { int power = block.getBlockState().getRedstonePower(); return target.set( (power + 5f) / 20f, 0f, 0f, @@ -138,15 +142,15 @@ public Color getRedstoneColor(BlockNeighborhood block, Color target) { ); } - public Color getWaterAverageColor(BlockNeighborhood block, Color target) { + public Color getBlendedWaterColor(BlockNeighborhood block, Color target) { target.set(0, 0, 0, 0, true); int x, y, z; Biome biome; - for (y = AVERAGE_MIN_Y; y <= AVERAGE_MAX_Y; y++) { - for (x = AVERAGE_MIN_X; x <= AVERAGE_MAX_X; x++) { - for (z = AVERAGE_MIN_Z; z <= AVERAGE_MAX_Z; z++) { + for (y = BLEND_MIN_Y; y <= BLEND_MAX_Y; y++) { + for (x = BLEND_MIN_X; x <= BLEND_MAX_X; x++) { + for (z = BLEND_MIN_Z; z <= BLEND_MAX_Z; z++) { biome = block.getNeighborBlock(x, y, z).getBiome(); target.add(biome.getWaterColor()); } @@ -156,15 +160,15 @@ public Color getWaterAverageColor(BlockNeighborhood block, Color target) { return target.flatten(); } - public Color getFoliageAverageColor(BlockNeighborhood block, Color target) { + public Color getBlendedFoliageColor(BlockNeighborhood block, Color target) { target.set(0, 0, 0, 0, true); int x, y, z; Biome biome; - for (y = AVERAGE_MIN_Y; y <= AVERAGE_MAX_Y; y++) { - for (x = AVERAGE_MIN_X; x <= AVERAGE_MAX_X; x++) { - for (z = AVERAGE_MIN_Z; z <= AVERAGE_MAX_Z; z++) { + for (y = BLEND_MIN_Y; y <= BLEND_MAX_Y; y++) { + for (x = BLEND_MIN_X; x <= BLEND_MAX_X; x++) { + for (z = BLEND_MIN_Z; z <= BLEND_MAX_Z; z++) { biome = block.getNeighborBlock(x, y, z).getBiome(); target.add(getFoliageColor(biome, tempColor)); } @@ -179,15 +183,15 @@ public Color getFoliageColor(Biome biome, Color target) { return target.overlay(biome.getOverlayFoliageColor()); } - public Color getGrassAverageColor(BlockNeighborhood block, Color target) { + public Color getBlendedGrassColor(BlockNeighborhood block, Color target) { target.set(0, 0, 0, 0, true); int x, y, z; Biome biome; - for (y = AVERAGE_MIN_Y; y <= AVERAGE_MAX_Y; y++) { - for (x = AVERAGE_MIN_X; x <= AVERAGE_MAX_X; x++) { - for (z = AVERAGE_MIN_Z; z <= AVERAGE_MAX_Z; z++) { + for (y = BLEND_MIN_Y; y <= BLEND_MAX_Y; y++) { + for (x = BLEND_MIN_X; x <= BLEND_MAX_X; x++) { + for (z = BLEND_MIN_Z; z <= BLEND_MAX_Z; z++) { biome = block.getNeighborBlock(x, y, z).getBiome(); target.add(getGrassColor(biome, tempColor)); } @@ -203,13 +207,13 @@ public Color getGrassColor(Biome biome, Color target) { } private void getColorFromMap(Biome biome, int[] colorMap, int defaultColor, Color target) { - double temperature = GenericMath.clamp(biome.getTemp(), 0.0, 1.0); - double humidity = GenericMath.clamp(biome.getHumidity(), 0.0, 1.0); + double temperature = GenericMath.clamp(biome.getTemperature(), 0.0, 1.0); + double downfall = GenericMath.clamp(biome.getDownfall(), 0.0, 1.0); - humidity *= temperature; + downfall *= temperature; int x = (int) ((1.0 - temperature) * 255.0); - int y = (int) ((1.0 - humidity) * 255.0); + int y = (int) ((1.0 - downfall) * 255.0); int index = y << 8 | x; int color = (index >= colorMap.length ? defaultColor : colorMap[index]) | 0xFF000000; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/MinecraftVersion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/MinecraftVersion.java new file mode 100644 index 00000000..949ea09f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/MinecraftVersion.java @@ -0,0 +1,245 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.resources; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import de.bluecolored.bluemap.api.debug.DebugDump; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.util.FileHelper; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URL; +import java.nio.file.*; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +@DebugDump +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class MinecraftVersion { + private static final Gson GSON = new Gson(); + + private static final String LATEST_KNOWN_VERSION = "1.20.6"; + private static final String EARLIEST_RESOURCEPACK_VERSION = "1.13"; + private static final String EARLIEST_DATAPACK_VERSION = "1.19.4"; + + private final String id; + + private final Path resourcePack; + private final int resourcePackVersion; + + private final Path dataPack; + private final int dataPackVersion; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MinecraftVersion that = (MinecraftVersion) o; + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + public static MinecraftVersion load(@Nullable String id, Path dataRoot, boolean allowDownload) throws IOException { + Path resourcePack; + Path dataPack; + + try { + VersionManifest manifest = VersionManifest.getOrFetch(); + if (id == null) id = manifest.getLatest().getRelease(); + + VersionManifest.Version version = manifest.getVersion(id); + VersionManifest.Version resourcePackVersion = manifest.getVersion(EARLIEST_RESOURCEPACK_VERSION); + VersionManifest.Version dataPackVersion = manifest.getVersion(EARLIEST_DATAPACK_VERSION); + + if (version == null) { + Logger.global.logWarning("Could not find any version for id '" + id + "'. Using fallback-version: " + LATEST_KNOWN_VERSION); + version = manifest.getVersion(LATEST_KNOWN_VERSION); + } + + if (version == null || resourcePackVersion == null || dataPackVersion == null) { + throw new IOException("Manifest is missing versions."); + } + + if (version.compareTo(resourcePackVersion) > 0) resourcePackVersion = version; + if (version.compareTo(dataPackVersion) > 0) dataPackVersion = version; + + resourcePack = dataRoot.resolve(getClientVersionFileName(resourcePackVersion.getId())); + dataPack = dataRoot.resolve(getClientVersionFileName(dataPackVersion.getId())); + + if (allowDownload) { + if (!Files.exists(resourcePack)) download(resourcePackVersion, resourcePack); + if (!Files.exists(dataPack)) download(dataPackVersion, dataPack); + } + + } catch (IOException ex) { + if (id == null) throw ex; + + Logger.global.logWarning("Failed to fetch version-info from mojang-servers: " + ex); + + resourcePack = dataRoot.resolve(getClientVersionFileName(id)); + dataPack = resourcePack; + } + + if (!Files.exists(resourcePack)) throw new IOException("Resource-File missing: " + resourcePack); + if (!Files.exists(dataPack)) throw new IOException("Resource-File missing: " + dataPack); + + VersionInfo resourcePackVersionInfo = loadVersionInfo(resourcePack); + VersionInfo dataPackVersionInfo = resourcePack.equals(dataPack) ? resourcePackVersionInfo : loadVersionInfo(dataPack); + + return new MinecraftVersion( + id, + resourcePack, resourcePackVersionInfo.getResourcePackVersion(), + dataPack, dataPackVersionInfo.getDataPackVersion() + ); + + } + + private static void download(VersionManifest.Version version, Path file) throws IOException { + boolean downloadCompletedAndVerified = false; + VersionManifest.Download download = version.fetchDetail().getDownloads().getClient(); + Logger.global.logInfo("Downloading '" + download.getUrl() + "' to '" + file + "'..."); + + FileHelper.createDirectories(file.toAbsolutePath().normalize().getParent()); + + try ( + DigestInputStream in = new DigestInputStream(new URL(download.getUrl()).openStream(), MessageDigest.getInstance("SHA-1")); + OutputStream out = Files.newOutputStream(file, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING) + ) { + in.transferTo(out); + + // verify sha-1 + if (!Arrays.equals( + in.getMessageDigest().digest(), + hexStringToByteArray(download.getSha1()) + )) { + throw new IOException("SHA-1 of the downloaded file does not match!"); + } + + downloadCompletedAndVerified = true; + } catch (NoSuchAlgorithmException | IOException ex) { + Logger.global.logWarning("Failed to download '" + download.getUrl() + "': " + ex); + } finally { + if (!downloadCompletedAndVerified) + Files.deleteIfExists(file); + } + + } + + private static String getClientVersionFileName(String versionId) { + return "minecraft-client-" + versionId + ".jar"; + } + + public static byte[] hexStringToByteArray(String hexString) { + int length = hexString.length(); + if (length % 2 != 0) + throw new IllegalArgumentException("Invalid hex-string."); + + int halfLength = length / 2; + + byte[] data = new byte[halfLength]; + int c; + for (int i = 0; i < halfLength; i += 1) { + c = i * 2; + data[i] = (byte) ( + (Character.digit(hexString.charAt(c), 16) << 4) + + Character.digit(hexString.charAt(c + 1), 16) + ); + } + + return data; + } + + private static VersionInfo loadVersionInfo(Path file) throws IOException { + try (FileSystem fileSystem = FileSystems.newFileSystem(file, (ClassLoader) null)) { + for (Path fsRoot : fileSystem.getRootDirectories()) { + if (!Files.isDirectory(fsRoot)) continue; + + Path versionFile = fsRoot.resolve("version.json"); + if (!Files.exists(versionFile)) continue; + + try (Reader reader = Files.newBufferedReader(file)) { + return GSON.fromJson(reader, VersionInfo.class); + } + } + + throw new IOException("'" + file + "' does not contain a 'version.json'"); + } + } + + @Getter + @RequiredArgsConstructor + @JsonAdapter(VersionInfoAdapter.class) + public static class VersionInfo { + + private final int resourcePackVersion; + private final int dataPackVersion; + + } + + public static class VersionInfoAdapter extends AbstractTypeAdapterFactory { + + public VersionInfoAdapter(Class type) { + super(type); + } + + @Override + public VersionInfo read(JsonReader in, Gson gson) throws IOException { + JsonObject object = gson.fromJson(in, JsonObject.class); + + JsonElement packVersion = object.get("pack_version"); + if (packVersion instanceof JsonObject packVersionObject) { + return new VersionInfo( + packVersionObject.get("resource").getAsInt(), + packVersionObject.get("data").getAsInt() + ); + } else { + int version = packVersion.getAsInt(); + return new VersionInfo( + version, + version + ); + } + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/PackMeta.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/PackMeta.java new file mode 100644 index 00000000..6af44dfd --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/PackMeta.java @@ -0,0 +1,86 @@ +package de.bluecolored.bluemap.core.resources; + +import com.google.gson.Gson; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; + +@Getter +@SuppressWarnings({"FieldMayBeFinal", "unused"}) +public class PackMeta { + + private Pack pack = new Pack(); + private Overlays overlays = new Overlays(); + + @Getter + public static class Pack { + private VersionRange packFormat = new VersionRange(); + private @Nullable VersionRange supportedFormats; + } + + @Getter + public static class Overlays { + private Overlay[] entries = new Overlay[0]; + } + + @Getter + public static class Overlay { + private VersionRange formats = new VersionRange(); + private @Nullable String directory; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @JsonAdapter(VersionRange.Adapter.class) + public static class VersionRange { + private int minInclusive = Integer.MIN_VALUE; + private int maxInclusive = Integer.MAX_VALUE; + + public boolean includes(int version) { + return version >= minInclusive && version <= maxInclusive; + } + + private static class Adapter extends AbstractTypeAdapterFactory { + + public Adapter(Class type) { + super(type); + } + + @Override + public VersionRange read(JsonReader in, Gson gson) throws IOException { + return switch (in.peek()) { + case NUMBER -> { + int version = in.nextInt(); + yield new VersionRange(version, version); + } + case BEGIN_ARRAY -> { + in.beginArray(); + VersionRange range = new VersionRange( + in.nextInt(), + in.nextInt() + ); + + while (in.peek() != JsonToken.END_ARRAY) + in.skipValue(); + in.endArray(); + + yield range; + } + default -> gson + .getDelegateAdapter(this, TypeToken.get(VersionRange.class)) + .read(in); + }; + } + + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/VersionManifest.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/VersionManifest.java new file mode 100644 index 00000000..a398264f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/VersionManifest.java @@ -0,0 +1,133 @@ +package de.bluecolored.bluemap.core.resources; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import de.bluecolored.bluemap.core.resources.adapter.LocalDateTimeAdapter; +import lombok.AccessLevel; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.*; +import java.net.URL; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +@Getter +@SuppressWarnings({"FieldMayBeFinal", "unused"}) +public class VersionManifest { + + public static final String DOMAIN = "https://piston-meta.mojang.com/"; + public static final String MANIFEST_URL = DOMAIN + "mc/game/version_manifest.json"; + + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter()) + .create(); + + private static VersionManifest instance; + + private Latest latest; + private Version[] versions; + + @Getter(AccessLevel.NONE) + private transient @Nullable Map versionMap; + + @Getter(AccessLevel.NONE) + private transient boolean sorted; + + + public static VersionManifest getOrFetch() throws IOException { + if (instance == null) return fetch(); + return instance; + } + + public static VersionManifest fetch() throws IOException { + try ( + InputStream in = new URL(MANIFEST_URL).openStream(); + Reader reader = new BufferedReader(new InputStreamReader(in)) + ) { + instance = GSON.fromJson(reader, VersionManifest.class); + } + return instance; + } + + /** + * An array of versions, ordered newest first + */ + public synchronized Version[] getVersions() { + if (!sorted) Arrays.sort(versions, Comparator.reverseOrder()); + return versions; + } + + public synchronized @Nullable Version getVersion(String id) { + if (versionMap == null) { + versionMap = new HashMap<>(); + for (Version version : versions) + versionMap.put(version.id, version); + } + + return versionMap.get(id); + } + + @Getter + public static class Latest { + private String release; + private String snapshot; + } + + @Getter + public static class Version implements Comparable { + + private String id; + private String type; + private String url; + private LocalDateTime time; + private LocalDateTime releaseTime; + + @Getter(AccessLevel.NONE) + private transient @Nullable VersionDetail detail; + + public synchronized VersionDetail fetchDetail() throws IOException { + if (detail == null) { + try ( + InputStream in = new URL(url).openStream(); + Reader reader = new BufferedReader(new InputStreamReader(in)) + ) { + detail = GSON.fromJson(reader, VersionDetail.class); + } + } + + return detail; + } + + @Override + public int compareTo(@NotNull VersionManifest.Version version) { + return releaseTime.compareTo(version.releaseTime); + } + + } + + @Getter + public static class VersionDetail { + private String id; + private String type; + private Downloads downloads; + } + + @Getter + public static class Downloads { + private Download client; + private Download server; + } + + @Getter + public static class Download { + private String url; + private long size; + private String sha1; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/LocalDateTimeAdapter.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/LocalDateTimeAdapter.java new file mode 100644 index 00000000..76c87314 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/LocalDateTimeAdapter.java @@ -0,0 +1,23 @@ +package de.bluecolored.bluemap.core.resources.adapter; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class LocalDateTimeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, LocalDateTime value) throws IOException { + out.value(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(value)); + } + + @Override + public LocalDateTime read(JsonReader in) throws IOException { + return LocalDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(in.nextString())); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java index 5dfc46e1..9577390b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/adapter/ResourcesGson.java @@ -29,7 +29,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.Face; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.util.math.Axis; import de.bluecolored.bluemap.core.util.math.Color; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/BiomeConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/BiomeConfig.java deleted file mode 100644 index c7d3a8f9..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/BiomeConfig.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.resources.biome; - -import com.google.gson.stream.JsonReader; -import de.bluecolored.bluemap.api.debug.DebugDump; -import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson; -import de.bluecolored.bluemap.core.resources.biome.datapack.DpBiome; -import de.bluecolored.bluemap.core.world.Biome; - -import java.io.BufferedReader; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; - -@DebugDump -public class BiomeConfig { - - private final Map biomes; - - public BiomeConfig() { - biomes = new HashMap<>(); - } - - public void load(Path configFile) throws IOException { - try (BufferedReader reader = Files.newBufferedReader(configFile)) { - JsonReader json = new JsonReader(reader); - json.setLenient(true); - - json.beginObject(); - - while (json.hasNext()) { - String formatted = json.nextName(); - BiomeConfigEntry entry = ResourcesGson.INSTANCE.fromJson(json, BiomeConfigEntry.class); - Biome biome = entry.createBiome(formatted); - - // don't overwrite already present values, higher priority resources are loaded first - biomes.putIfAbsent(biome.getFormatted(), biome); - } - - json.endObject(); - } - } - - public void loadDatapackBiome(String namespace, Path biomeFile) throws IOException { - try (BufferedReader reader = Files.newBufferedReader(biomeFile)) { - JsonReader json = new JsonReader(reader); - json.setLenient(true); - DpBiome dpBiome = ResourcesGson.INSTANCE.fromJson(json, DpBiome.class); - - String formatted = namespace + ":" + biomeFile.getFileName().toString(); - int fileEndingDot = formatted.lastIndexOf('.'); - if (fileEndingDot != -1) formatted = formatted.substring(0, fileEndingDot); - - Biome biome = dpBiome.createBiome(formatted); - - // don't overwrite already present values, higher priority resources are loaded first - biomes.putIfAbsent(biome.getFormatted(), biome); - } - } - - public Biome getBiome(String formatted) { - return biomes.getOrDefault(formatted, Biome.DEFAULT); - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/BiomeConfigEntry.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/BiomeConfigEntry.java deleted file mode 100644 index 0b93d881..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/BiomeConfigEntry.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.resources.biome; - -import de.bluecolored.bluemap.core.util.math.Color; -import de.bluecolored.bluemap.core.world.Biome; - -@SuppressWarnings("FieldMayBeFinal") -public class BiomeConfigEntry { - - private float humidity = Biome.DEFAULT.getHumidity(); - private float temperature = Biome.DEFAULT.getTemp(); - private Color watercolor = Biome.DEFAULT.getWaterColor(); - private Color grasscolor = new Color(); - private Color foliagecolor = new Color(); - - public Biome createBiome(String formatted) { - return new Biome( - formatted, - humidity, - temperature, - watercolor.premultiplied(), - foliagecolor.premultiplied(), - grasscolor.premultiplied() - ); - } - - public float getHumidity() { - return humidity; - } - - public float getTemperature() { - return temperature; - } - - public Color getWatercolor() { - return watercolor; - } - - public Color getGrasscolor() { - return grasscolor; - } - - public Color getFoliagecolor() { - return foliagecolor; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiomeEffects.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiomeEffects.java deleted file mode 100644 index 807b80b6..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/biome/datapack/DpBiomeEffects.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.resources.biome.datapack; - -import de.bluecolored.bluemap.core.util.math.Color; -import de.bluecolored.bluemap.core.world.Biome; -import lombok.Getter; - -@SuppressWarnings("FieldMayBeFinal") -@Getter -public class DpBiomeEffects { - - private Color waterColor = Biome.DEFAULT.getWaterColor(); - private Color foliageColor = Biome.DEFAULT.getOverlayFoliageColor(); - private Color grassColor = Biome.DEFAULT.getOverlayGrassColor(); - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/Pack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/Pack.java new file mode 100644 index 00000000..f37b712f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/Pack.java @@ -0,0 +1,125 @@ +package de.bluecolored.bluemap.core.resources.pack; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resources.PackMeta; +import de.bluecolored.bluemap.core.resources.ResourcePath; +import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.concurrent.CompletionException; +import java.util.stream.Stream; + +@RequiredArgsConstructor +@Getter +public abstract class Pack { + + private final int packVersion; + + public abstract void loadResources(Iterable roots) throws IOException, InterruptedException; + + protected void loadResourcePath(Path root, ResourcePack.PathLoader resourceLoader) throws IOException, InterruptedException { + if (Thread.interrupted()) throw new InterruptedException(); + if (!Files.isDirectory(root)) { + try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) { + for (Path fsRoot : fileSystem.getRootDirectories()) { + if (!Files.isDirectory(fsRoot)) continue; + loadResourcePath(fsRoot, resourceLoader); + } + } catch (Exception ex) { + Logger.global.logDebug("Failed to read '" + root + "': " + ex); + } + return; + } + + // load nested jars from fabric.mod.json if present + Path fabricModJson = root.resolve("fabric.mod.json"); + if (Files.isRegularFile(fabricModJson)) { + try (BufferedReader reader = Files.newBufferedReader(fabricModJson)) { + JsonObject rootElement = ResourcesGson.INSTANCE.fromJson(reader, JsonObject.class); + if (rootElement.has("jars")) { + for (JsonElement element : rootElement.getAsJsonArray("jars")) { + Path file = root.resolve(element.getAsJsonObject().get("file").getAsString()); + if (Files.exists(file)) loadResourcePath(file, resourceLoader); + } + } + } catch (Exception ex) { + Logger.global.logDebug("Failed to read fabric.mod.json: " + ex); + } + } + + // load overlays + Path packMetaFile = root.resolve("pack.mcmeta"); + if (Files.isRegularFile(packMetaFile)) { + try (BufferedReader reader = Files.newBufferedReader(packMetaFile)) { + PackMeta packMeta = ResourcesGson.INSTANCE.fromJson(reader, PackMeta.class); + PackMeta.Overlay[] overlays = packMeta.getOverlays().getEntries(); + for (int i = overlays.length - 1; i >= 0; i--) { + PackMeta.Overlay overlay = overlays[i]; + String dir = overlay.getDirectory(); + if (dir != null && overlay.getFormats().includes(this.packVersion)) { + Path overlayRoot = root.resolve(dir); + if (Files.exists(overlayRoot)) loadResourcePath(overlayRoot, resourceLoader); + } + } + } catch (Exception ex) { + Logger.global.logDebug("Failed to read pack.mcmeta: " + ex); + } + } + + resourceLoader.load(root); + } + + protected void loadResource(Path root, Path file, Loader loader, Map, T> resultMap) { + try { + ResourcePath resourcePath = new ResourcePath<>(root.relativize(file)); + if (resultMap.containsKey(resourcePath)) return; // don't load already present resources + + T resource = loader.load(resourcePath); + if (resource == null) return; // don't load missing resources + + resourcePath.setResource(resource); + resultMap.put(resourcePath, resource); + } catch (Exception ex) { + Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex); + } + } + + protected static Stream list(Path root) { + if (!Files.isDirectory(root)) return Stream.empty(); + try { + return Files.list(root); + } catch (IOException ex) { + throw new CompletionException(ex); + } + } + + protected static Stream walk(Path root) { + if (!Files.exists(root)) return Stream.empty(); + if (Files.isRegularFile(root)) return Stream.of(root); + try { + return Files.walk(root); + } catch (IOException ex) { + throw new CompletionException(ex); + } + } + + protected interface Loader { + T load(ResourcePath resourcePath) throws IOException; + } + + protected interface PathLoader { + void load(Path root) throws IOException; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/DataPack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/DataPack.java similarity index 57% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/DataPack.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/DataPack.java index 632565cb..9653a17c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/DataPack.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/DataPack.java @@ -22,28 +22,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.datapack; +package de.bluecolored.bluemap.core.resources.pack.datapack; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.resources.ResourcePath; import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson; -import de.bluecolored.bluemap.core.resources.datapack.dimension.DimensionTypeData; +import de.bluecolored.bluemap.core.resources.pack.Pack; +import de.bluecolored.bluemap.core.resources.pack.datapack.biome.DatapackBiome; +import de.bluecolored.bluemap.core.resources.pack.datapack.dimension.DimensionTypeData; import de.bluecolored.bluemap.core.util.Key; +import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.DimensionType; +import de.bluecolored.bluemap.core.world.mca.chunk.LegacyBiomes; import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletionException; -import java.util.stream.Stream; -public class DataPack { +public class DataPack extends Pack { public static final Key DIMENSION_OVERWORLD = new Key("minecraft", "overworld"); public static final Key DIMENSION_THE_NETHER = new Key("minecraft", "the_nether"); @@ -55,57 +54,57 @@ public class DataPack { public static final Key DIMENSION_TYPE_THE_END = new Key("minecraft", "the_end"); private final Map dimensionTypes = new HashMap<>(); + private final Map biomes = new HashMap<>(); - @Nullable - public DimensionType getDimensionType(Key key) { - return dimensionTypes.get(key); + private LegacyBiomes legacyBiomes; + + public DataPack(int packVersion) { + super(packVersion); } - public void load(Path root) throws InterruptedException { - Logger.global.logDebug("Loading datapack from: " + root + " ..."); - loadPath(root); - } + @Override + public void loadResources(Iterable roots) throws IOException, InterruptedException { + Logger.global.logInfo("Loading datapack..."); - private void loadPath(Path root) throws InterruptedException { - if (Thread.interrupted()) throw new InterruptedException(); - if (!Files.isDirectory(root)) { - try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) { - for (Path fsRoot : fileSystem.getRootDirectories()) { - if (!Files.isDirectory(fsRoot)) continue; - loadPath(fsRoot); - } - } catch (Exception ex) { - Logger.global.logDebug("Failed to read '" + root + "': " + ex); - } - return; + for (Path root : roots) { + Logger.global.logDebug("Loading datapack from: " + root + " ..."); + loadResources(root); } + Logger.global.logInfo("Baking datapack..."); + bake(); + + Logger.global.logInfo("Datapack loaded."); + } + + private void loadResources(Path root) throws InterruptedException, IOException { + loadResourcePath(root, this::loadPath); + } + + private void loadPath(Path root) { list(root.resolve("data")) .map(path -> path.resolve("dimension_type")) .filter(Files::isDirectory) .flatMap(DataPack::walk) .filter(path -> path.getFileName().toString().endsWith(".json")) .filter(Files::isRegularFile) - .forEach(file -> loadResource(root, file, () -> { + .forEach(file -> loadResource(root, file, key -> { try (BufferedReader reader = Files.newBufferedReader(file)) { return ResourcesGson.INSTANCE.fromJson(reader, DimensionTypeData.class); } }, dimensionTypes)); - } - private void loadResource(Path root, Path file, Loader loader, Map resultMap) { - try { - ResourcePath resourcePath = new ResourcePath<>(root.relativize(file)); - if (resultMap.containsKey(resourcePath)) return; // don't load already present resources - - T resource = loader.load(); - if (resource == null) return; // don't load missing resources - - resourcePath.setResource(resource); - resultMap.put(resourcePath, resource); - } catch (Exception ex) { - Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex); - } + list(root.resolve("data")) + .map(path -> path.resolve("worldgen").resolve("biome")) + .filter(Files::isDirectory) + .flatMap(DataPack::walk) + .filter(path -> path.getFileName().toString().endsWith(".json")) + .filter(Files::isRegularFile) + .forEach(file -> loadResource(root, file, key -> { + try (BufferedReader reader = Files.newBufferedReader(file)) { + return new DatapackBiome(key, ResourcesGson.INSTANCE.fromJson(reader, DatapackBiome.Data.class)); + } + }, biomes)); } public void bake() { @@ -113,29 +112,20 @@ public void bake() { dimensionTypes.putIfAbsent(DIMENSION_TYPE_OVERWORLD_CAVES, DimensionType.OVERWORLD_CAVES); dimensionTypes.putIfAbsent(DIMENSION_TYPE_THE_NETHER, DimensionType.NETHER); dimensionTypes.putIfAbsent(DIMENSION_TYPE_THE_END, DimensionType.END); + + legacyBiomes = new LegacyBiomes(this); } - private static Stream list(Path root) { - if (!Files.isDirectory(root)) return Stream.empty(); - try { - return Files.list(root); - } catch (IOException ex) { - throw new CompletionException(ex); - } + public @Nullable DimensionType getDimensionType(Key key) { + return dimensionTypes.get(key); } - private static Stream walk(Path root) { - if (!Files.exists(root)) return Stream.empty(); - if (Files.isRegularFile(root)) return Stream.of(root); - try { - return Files.walk(root); - } catch (IOException ex) { - throw new CompletionException(ex); - } + public @Nullable Biome getBiome(Key key) { + return biomes.get(key); } - private interface Loader { - T load() throws IOException; + public @Nullable Biome getBiome(int legacyId) { + return legacyBiomes.forId(legacyId); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/biome/DatapackBiome.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/biome/DatapackBiome.java new file mode 100644 index 00000000..293530a8 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/biome/DatapackBiome.java @@ -0,0 +1,61 @@ +package de.bluecolored.bluemap.core.resources.pack.datapack.biome; + +import de.bluecolored.bluemap.core.util.Key; +import de.bluecolored.bluemap.core.util.math.Color; +import de.bluecolored.bluemap.core.world.Biome; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class DatapackBiome implements Biome { + + private final Key key; + private final Data data; + + @Override + public float getDownfall() { + return data.downfall; + } + + @Override + public float getTemperature() { + return data.temperature; + } + + @Override + public Color getWaterColor() { + return data.effects.waterColor; + } + + @Override + public Color getOverlayFoliageColor() { + return data.effects.foliageColor; + } + + @Override + public Color getOverlayGrassColor() { + return data.effects.grassColor; + } + + @SuppressWarnings("FieldMayBeFinal") + @Getter + public static class Data { + + private Effects effects = new Effects(); + private float temperature = Biome.DEFAULT.getTemperature(); + private float downfall = Biome.DEFAULT.getDownfall(); + + } + + @SuppressWarnings("FieldMayBeFinal") + @Getter + public static class Effects { + + private Color waterColor = Biome.DEFAULT.getWaterColor(); + private Color foliageColor = Biome.DEFAULT.getOverlayFoliageColor(); + private Color grassColor = Biome.DEFAULT.getOverlayGrassColor(); + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/dimension/DimensionTypeData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/dimension/DimensionTypeData.java similarity index 96% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/dimension/DimensionTypeData.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/dimension/DimensionTypeData.java index 44199368..647b8f18 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/datapack/dimension/DimensionTypeData.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/datapack/dimension/DimensionTypeData.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.datapack.dimension; +package de.bluecolored.bluemap.core.resources.pack.datapack.dimension; import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.world.DimensionType; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/ResourcePack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/ResourcePack.java similarity index 68% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/ResourcePack.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/ResourcePack.java index 06df4809..d1eff679 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/ResourcePack.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/ResourcePack.java @@ -22,12 +22,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack; +package de.bluecolored.bluemap.core.resources.pack.resourcepack; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.logger.Logger; @@ -35,14 +33,13 @@ import de.bluecolored.bluemap.core.resources.BlockPropertiesConfig; import de.bluecolored.bluemap.core.resources.ResourcePath; import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson; -import de.bluecolored.bluemap.core.resources.biome.BiomeConfig; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.TextureVariable; -import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.BlockState; -import de.bluecolored.bluemap.core.resources.resourcepack.texture.AnimationMeta; -import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture; +import de.bluecolored.bluemap.core.resources.pack.Pack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.BlockModel; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.TextureVariable; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate.BlockState; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.AnimationMeta; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.Texture; import de.bluecolored.bluemap.core.util.Tristate; -import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockProperties; import org.jetbrains.annotations.Nullable; @@ -53,8 +50,6 @@ import java.io.InputStream; import java.io.Reader; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; @@ -62,40 +57,36 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.stream.Stream; @DebugDump -public class ResourcePack { +public class ResourcePack extends Pack { public static final ResourcePath MISSING_BLOCK_STATE = new ResourcePath<>("bluemap", "missing"); public static final ResourcePath MISSING_BLOCK_MODEL = new ResourcePath<>("bluemap", "block/missing"); public static final ResourcePath MISSING_TEXTURE = new ResourcePath<>("bluemap", "block/missing"); - private final Map> blockStatePaths; private final Map, BlockState> blockStates; - private final Map> blockModelPaths; private final Map, BlockModel> blockModels; - private final Map> texturePaths; private final Map, Texture> textures; - private final Map, BufferedImage> colormaps; + private final Map, BufferedImage> colormaps; private final BlockColorCalculatorFactory colorCalculatorFactory; - private final BiomeConfig biomeConfig; private final BlockPropertiesConfig blockPropertiesConfig; + private final Map> blockStatePaths; + private final Map> texturePaths; private final LoadingCache blockPropertiesCache; - public ResourcePack() { + public ResourcePack(int packVersion) { + super(packVersion); + this.blockStatePaths = new HashMap<>(); this.blockStates = new HashMap<>(); - this.blockModelPaths = new HashMap<>(); this.blockModels = new HashMap<>(); this.texturePaths = new HashMap<>(); this.textures = new HashMap<>(); this.colormaps = new HashMap<>(); this.colorCalculatorFactory = new BlockColorCalculatorFactory(); - this.biomeConfig = new BiomeConfig(); this.blockPropertiesConfig = new BlockPropertiesConfig(); this.blockPropertiesCache = Caffeine.newBuilder() @@ -104,88 +95,6 @@ public ResourcePack() { .build(this::loadBlockProperties); } - @Nullable - public ResourcePath getBlockStatePath(String formatted) { - return blockStatePaths.get(formatted); - } - - @Nullable - public BlockState getBlockState(de.bluecolored.bluemap.core.world.BlockState blockState) { - ResourcePath path = blockStatePaths.get(blockState.getFormatted()); - return path != null ? path.getResource(this::getBlockState) : MISSING_BLOCK_STATE.getResource(this::getBlockState); - } - - @Nullable - public BlockState getBlockState(ResourcePath path) { - BlockState blockState = blockStates.get(path); - return blockState != null ? blockState : MISSING_BLOCK_STATE.getResource(blockStates::get); - } - - public Map, BlockState> getBlockStates() { - return blockStates; - } - - @Nullable - public ResourcePath getBlockModelPath(String formatted) { - return blockModelPaths.get(formatted); - } - - @Nullable - public BlockModel getBlockModel(ResourcePath path) { - BlockModel blockModel = blockModels.get(path); - return blockModel != null ? blockModel : MISSING_BLOCK_MODEL.getResource(blockModels::get); - } - - public Map, BlockModel> getBlockModels() { - return blockModels; - } - - @Nullable - public ResourcePath getTexturePath(String formatted) { - return texturePaths.get(formatted); - } - - @Nullable - public Texture getTexture(ResourcePath path) { - Texture texture = textures.get(path); - return texture != null ? texture : MISSING_TEXTURE.getResource(textures::get); - } - - public Map, Texture> getTextures() { - return textures; - } - - public BlockColorCalculatorFactory getColorCalculatorFactory() { - return colorCalculatorFactory; - } - - public Biome getBiome(String formatted) { - return biomeConfig.getBiome(formatted); - } - - public BlockProperties getBlockProperties(de.bluecolored.bluemap.core.world.BlockState state) { - return blockPropertiesCache.get(state); - } - - private BlockProperties loadBlockProperties(de.bluecolored.bluemap.core.world.BlockState state) { - BlockProperties.Builder props = blockPropertiesConfig.getBlockProperties(state).toBuilder(); - - if (props.isOccluding() == Tristate.UNDEFINED || props.isCulling() == Tristate.UNDEFINED) { - BlockState resource = getBlockState(state); - if (resource != null) { - resource.forEach(state,0, 0, 0, variant -> { - BlockModel model = variant.getModel().getResource(this::getBlockModel); - if (model != null) { - if (props.isOccluding() == Tristate.UNDEFINED) props.occluding(model.isOccluding()); - if (props.isCulling() == Tristate.UNDEFINED) props.culling(model.isCulling()); - } - }); - } - } - - return props.build(); - } - public synchronized void loadResources(Iterable roots) throws IOException, InterruptedException { Logger.global.logInfo("Loading resources..."); @@ -209,43 +118,9 @@ public synchronized void loadResources(Iterable roots) throws IOException, Logger.global.logInfo("Baking resources..."); bake(); - Logger.global.logInfo("Resources loaded."); } - private void loadResourcePath(Path root, PathLoader resourceLoader) throws IOException, InterruptedException { - if (Thread.interrupted()) throw new InterruptedException(); - if (!Files.isDirectory(root)) { - try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) { - for (Path fsRoot : fileSystem.getRootDirectories()) { - if (!Files.isDirectory(fsRoot)) continue; - loadResourcePath(fsRoot, resourceLoader); - } - } catch (Exception ex) { - Logger.global.logDebug("Failed to read '" + root + "': " + ex); - } - return; - } - - // load nested jars from fabric.mod.json if present - Path fabricModJson = root.resolve("fabric.mod.json"); - if (Files.isRegularFile(fabricModJson)) { - try (BufferedReader reader = Files.newBufferedReader(fabricModJson)) { - JsonObject rootElement = ResourcesGson.INSTANCE.fromJson(reader, JsonObject.class); - if (rootElement.has("jars")) { - for (JsonElement element : rootElement.getAsJsonArray("jars")) { - Path file = root.resolve(element.getAsJsonObject().get("file").getAsString()); - if (Files.exists(file)) loadResourcePath(file, resourceLoader); - } - } - } catch (Exception ex) { - Logger.global.logDebug("Failed to read fabric.mod.json: " + ex); - } - } - - resourceLoader.load(root); - } - private void loadResources(Path root) throws IOException { try { // do those in parallel @@ -259,7 +134,7 @@ private void loadResources(Path root) throws IOException { .flatMap(ResourcePack::walk) .filter(path -> path.getFileName().toString().endsWith(".json")) .filter(Files::isRegularFile) - .forEach(file -> loadResource(root, file, () -> { + .forEach(file -> loadResource(root, file, key -> { try (BufferedReader reader = Files.newBufferedReader(file)) { return ResourcesGson.INSTANCE.fromJson(reader, BlockState.class); } @@ -275,7 +150,7 @@ private void loadResources(Path root) throws IOException { .flatMap(ResourcePack::walk) .filter(path -> path.getFileName().toString().endsWith(".json")) .filter(Files::isRegularFile) - .forEach(file -> loadResource(root, file, () -> { + .forEach(file -> loadResource(root, file, key -> { try (BufferedReader reader = Files.newBufferedReader(file)) { return ResourcesGson.INSTANCE.fromJson(reader, BlockModel.class); } @@ -287,7 +162,7 @@ private void loadResources(Path root) throws IOException { walk(root.resolve("assets").resolve("minecraft").resolve("textures").resolve("colormap")) .filter(path -> path.getFileName().toString().endsWith(".png")) .filter(Files::isRegularFile) - .forEach(file -> loadResource(root, file, () -> { + .forEach(file -> loadResource(root, file, key -> { try (InputStream in = Files.newInputStream(file)) { return ImageIO.read(in); } @@ -308,35 +183,6 @@ private void loadResources(Path root) throws IOException { }); }, BlueMap.THREAD_POOL), - // load biome configs - // TODO: move this to datapacks? - CompletableFuture.runAsync(() -> { - list(root.resolve("assets")) - .map(path -> path.resolve("biomes.json")) - .filter(Files::isRegularFile) - .forEach(file -> { - try { - biomeConfig.load(file); - } catch (Exception ex) { - Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex); - } - }); - - list(root.resolve("data")) - .filter(Files::isDirectory) - .forEach(namespace -> list(namespace.resolve("worldgen").resolve("biome")) - .filter(path -> path.getFileName().toString().endsWith(".json")) - .filter(Files::isRegularFile) - .forEach(file -> { - try { - biomeConfig.loadDatapackBiome(namespace.getFileName().toString(), file); - } catch (Exception ex) { - Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex); - } - }) - ); - }, BlueMap.THREAD_POOL), - // load block-properties configs CompletableFuture.runAsync(() -> { list(root.resolve("assets")) @@ -380,9 +226,8 @@ private void loadTextures(Path root) throws IOException { .flatMap(ResourcePack::walk) .filter(path -> path.getFileName().toString().endsWith(".png")) .filter(Files::isRegularFile) - .forEach(file -> loadResource(root, file, () -> { - ResourcePath resourcePath = new ResourcePath<>(root.relativize(file)); - if (!usedTextures.contains(resourcePath)) return null; // don't load unused textures + .forEach(file -> loadResource(root, file, key -> { + if (!usedTextures.contains(key)) return null; // don't load unused textures // load image BufferedImage image; @@ -399,8 +244,7 @@ private void loadTextures(Path root) throws IOException { } } - return Texture.from(resourcePath, image, animation); - + return Texture.from(key, image, animation); }, textures)); } catch (RuntimeException ex) { @@ -415,7 +259,6 @@ private void bake() throws IOException, InterruptedException { // fill path maps blockStates.keySet().forEach(path -> blockStatePaths.put(path.getFormatted(), path)); - blockModels.keySet().forEach(path -> blockModelPaths.put(path.getFormatted(), path)); textures.keySet().forEach(path -> texturePaths.put(path.getFormatted(), path)); // optimize references @@ -447,46 +290,64 @@ private void bake() throws IOException, InterruptedException { } - private void loadResource(Path root, Path file, Loader loader, Map, T> resultMap) { - try { - ResourcePath resourcePath = new ResourcePath<>(root.relativize(file)); - if (resultMap.containsKey(resourcePath)) return; // don't load already present resources + @Nullable + public BlockState getBlockState(de.bluecolored.bluemap.core.world.BlockState blockState) { + ResourcePath path = blockStatePaths.get(blockState.getFormatted()); + return path != null ? path.getResource(this::getBlockState) : MISSING_BLOCK_STATE.getResource(this::getBlockState); + } - T resource = loader.load(); - if (resource == null) return; // don't load missing resources + @Nullable + public BlockState getBlockState(ResourcePath path) { + BlockState blockState = blockStates.get(path); + return blockState != null ? blockState : MISSING_BLOCK_STATE.getResource(blockStates::get); + } - resourcePath.setResource(resource); - resultMap.put(resourcePath, resource); - } catch (Exception ex) { - Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex); + @Nullable + public BlockModel getBlockModel(ResourcePath path) { + BlockModel blockModel = blockModels.get(path); + return blockModel != null ? blockModel : MISSING_BLOCK_MODEL.getResource(blockModels::get); + } + + @Nullable + public ResourcePath getTexturePath(String formatted) { + return texturePaths.get(formatted); + } + + @Nullable + public Texture getTexture(ResourcePath path) { + Texture texture = textures.get(path); + return texture != null ? texture : MISSING_TEXTURE.getResource(textures::get); + } + + public Map, Texture> getTextures() { + return textures; + } + + public BlockColorCalculatorFactory getColorCalculatorFactory() { + return colorCalculatorFactory; + } + + public BlockProperties getBlockProperties(de.bluecolored.bluemap.core.world.BlockState state) { + return blockPropertiesCache.get(state); + } + + private BlockProperties loadBlockProperties(de.bluecolored.bluemap.core.world.BlockState state) { + BlockProperties.Builder props = blockPropertiesConfig.getBlockProperties(state).toBuilder(); + + if (props.isOccluding() == Tristate.UNDEFINED || props.isCulling() == Tristate.UNDEFINED) { + BlockState resource = getBlockState(state); + if (resource != null) { + resource.forEach(state,0, 0, 0, variant -> { + BlockModel model = variant.getModel().getResource(this::getBlockModel); + if (model != null) { + if (props.isOccluding() == Tristate.UNDEFINED) props.occluding(model.isOccluding()); + if (props.isCulling() == Tristate.UNDEFINED) props.culling(model.isCulling()); + } + }); + } } - } - private static Stream list(Path root) { - if (!Files.isDirectory(root)) return Stream.empty(); - try { - return Files.list(root); - } catch (IOException ex) { - throw new CompletionException(ex); - } - } - - private static Stream walk(Path root) { - if (!Files.exists(root)) return Stream.empty(); - if (Files.isRegularFile(root)) return Stream.of(root); - try { - return Files.walk(root); - } catch (IOException ex) { - throw new CompletionException(ex); - } - } - - private interface Loader { - T load() throws IOException; - } - - private interface PathLoader { - void load(Path root) throws IOException; + return props.build(); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/BlockModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/BlockModel.java similarity index 95% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/BlockModel.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/BlockModel.java index 23a3eba3..00dbae8d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/BlockModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/BlockModel.java @@ -22,12 +22,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel; import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.resources.ResourcePath; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.Texture; import de.bluecolored.bluemap.core.util.Direction; import org.jetbrains.annotations.Nullable; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Element.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Element.java similarity index 97% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Element.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Element.java index aa74e186..a022d063 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Element.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Element.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel; import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector4f; @@ -32,7 +32,7 @@ import com.google.gson.stream.JsonReader; import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.util.Direction; import java.io.IOException; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Face.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Face.java similarity index 95% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Face.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Face.java index b5956a8b..5c6ca23a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Face.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Face.java @@ -22,11 +22,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel; import com.flowpowered.math.vector.Vector4f; import de.bluecolored.bluemap.api.debug.DebugDump; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.util.Direction; import java.util.function.Function; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Rotation.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Rotation.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Rotation.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Rotation.java index fe3920d1..009d9993 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/Rotation.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/Rotation.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel; import com.flowpowered.math.TrigMath; import com.flowpowered.math.vector.Vector3f; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/TextureVariable.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/TextureVariable.java similarity index 95% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/TextureVariable.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/TextureVariable.java index 6984d501..f29b5996 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockmodel/TextureVariable.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockmodel/TextureVariable.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel; import com.google.gson.TypeAdapter; import com.google.gson.annotations.JsonAdapter; @@ -30,8 +30,8 @@ import com.google.gson.stream.JsonWriter; import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.resources.ResourcePath; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.texture.Texture; import org.jetbrains.annotations.Nullable; import java.io.IOException; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/BlockState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/BlockState.java similarity index 96% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/BlockState.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/BlockState.java index 2a83f470..26b01521 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/BlockState.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/BlockState.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockstate; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate; import de.bluecolored.bluemap.api.debug.DebugDump; import org.jetbrains.annotations.Nullable; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/BlockStateCondition.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/BlockStateCondition.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/BlockStateCondition.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/BlockStateCondition.java index 4663e214..85890749 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/BlockStateCondition.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/BlockStateCondition.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockstate; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate; import de.bluecolored.bluemap.api.debug.DebugDump; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Multipart.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Multipart.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Multipart.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Multipart.java index bdcc2dc3..87223503 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Multipart.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Multipart.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockstate; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate; import com.google.gson.Gson; import com.google.gson.annotations.JsonAdapter; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Variant.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Variant.java similarity index 92% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Variant.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Variant.java index 192cc914..ed491195 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Variant.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Variant.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockstate; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate; import com.google.gson.Gson; import com.google.gson.annotations.JsonAdapter; @@ -31,8 +31,8 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory; import de.bluecolored.bluemap.core.resources.ResourcePath; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.blockmodel.BlockModel; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.util.math.MatrixM3f; import java.io.IOException; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/VariantSet.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/VariantSet.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/VariantSet.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/VariantSet.java index 3275cbe5..b1e51482 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/VariantSet.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/VariantSet.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockstate; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate; import com.google.gson.Gson; import com.google.gson.annotations.JsonAdapter; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Variants.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Variants.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Variants.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Variants.java index f301c48b..0630025b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/blockstate/Variants.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/blockstate/Variants.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.blockstate; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.blockstate; import com.google.gson.Gson; import com.google.gson.annotations.JsonAdapter; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/texture/AnimationMeta.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/texture/AnimationMeta.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/texture/AnimationMeta.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/texture/AnimationMeta.java index 3aec544e..2ff9bc3c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/texture/AnimationMeta.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/texture/AnimationMeta.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.texture; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.texture; import com.google.gson.Gson; import com.google.gson.annotations.JsonAdapter; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/texture/Texture.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/texture/Texture.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/texture/Texture.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/texture/Texture.java index 0e5c58f3..ea7fcb87 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/resourcepack/texture/Texture.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resources/pack/resourcepack/texture/Texture.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.resources.resourcepack.texture; +package de.bluecolored.bluemap.core.resources.pack.resourcepack.texture; import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.resources.ResourcePath; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/GridStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/GridStorage.java index 83a8566b..baaac7e6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/GridStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/GridStorage.java @@ -62,6 +62,11 @@ public interface GridStorage { */ boolean exists(int x, int z) throws IOException; + /** + * Returns a {@link ItemStorage} for the given position + */ + ItemStorage cell(int x, int z); + /** * Returns a stream over all existing items in this storage */ @@ -72,7 +77,7 @@ public interface GridStorage { */ boolean isClosed(); - interface Cell extends SingleItemStorage { + interface Cell extends ItemStorage { /** * Returns the x position of this item in the grid diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/SingleItemStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/ItemStorage.java similarity index 98% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/SingleItemStorage.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/ItemStorage.java index 4d4338aa..698b7629 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/SingleItemStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/ItemStorage.java @@ -30,7 +30,7 @@ import java.io.IOException; import java.io.OutputStream; -public interface SingleItemStorage { +public interface ItemStorage { /** * Returns an {@link OutputStream} that can be used to write the item-data of this storage diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/KeyedMapStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/KeyedMapStorage.java new file mode 100644 index 00000000..c0e56565 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/KeyedMapStorage.java @@ -0,0 +1,77 @@ +package de.bluecolored.bluemap.core.storage; + +import de.bluecolored.bluemap.core.storage.compression.Compression; +import de.bluecolored.bluemap.core.util.Key; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public abstract class KeyedMapStorage implements MapStorage { + + private static final Key HIRES_TILES_KEY = Key.bluemap("hires"); + private static final Key TILE_STATE_KEY = Key.bluemap("tile-state"); + private static final Key CHUNK_STATE_KEY = Key.bluemap("chunk-state"); + private static final Key SETTINGS_KEY = Key.bluemap("settings"); + private static final Key TEXTURES_KEY = Key.bluemap("textures"); + private static final Key MARKERS_KEY = Key.bluemap("markers"); + private static final Key PLAYERS_KEY = Key.bluemap("players"); + + private final Compression compression; + + @Override + public GridStorage hiresTiles() { + return grid(HIRES_TILES_KEY, compression); + } + + @Override + public GridStorage lowresTiles(int lod) { + return grid(Key.bluemap("lowres/" + lod), Compression.NONE); + } + + @Override + public GridStorage tileState() { + return grid(TILE_STATE_KEY, Compression.GZIP); + } + + @Override + public GridStorage chunkState() { + return grid(CHUNK_STATE_KEY, Compression.GZIP); + } + + @Override + public ItemStorage asset(String name) { + return item(Key.bluemap("asset/" + MapStorage.escapeAssetName(name)), Compression.NONE); + } + + @Override + public ItemStorage settings() { + return item(SETTINGS_KEY, Compression.NONE); + } + + @Override + public ItemStorage textures() { + return item(TEXTURES_KEY, compression); + } + + @Override + public ItemStorage markers() { + return item(MARKERS_KEY, Compression.NONE); + } + + @Override + public ItemStorage players() { + return item(PLAYERS_KEY, Compression.NONE); + } + + /** + * Returns a {@link GridStorage} for the given {@link Key}.
+ * The compressionHint can be used if a new {@link GridStorage} needs to be created, but is not guaranteed. + */ + public abstract GridStorage grid(Key key, Compression compressionHint); + + /** + * Returns a {@link ItemStorage} for the given {@link Key}.
+ * The compressionHint can be used if a new {@link ItemStorage} needs to be created, but is not guaranteed. + */ + public abstract ItemStorage item(Key key, Compression compressionHint); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MapStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MapStorage.java index bc280402..711e0ee2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MapStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MapStorage.java @@ -40,34 +40,39 @@ public interface MapStorage { GridStorage lowresTiles(int lod); /** - * Returns a {@link SingleItemStorage} for a map asset with the given name + * Returns a {@link GridStorage} for the tile-state (meta-) data of this map */ - SingleItemStorage asset(String name); + GridStorage tileState(); /** - * Returns a {@link SingleItemStorage} for the render-state data of this map + * Returns a {@link GridStorage} for the chunk-state (meta-) data of this map */ - SingleItemStorage renderState(); + GridStorage chunkState(); /** - * Returns a {@link SingleItemStorage} for the settings (settings.json) of this map + * Returns a {@link ItemStorage} for a map asset with the given name */ - SingleItemStorage settings(); + ItemStorage asset(String name); /** - * Returns a {@link SingleItemStorage} for the texture-data (textures.json) of this map + * Returns a {@link ItemStorage} for the settings (settings.json) of this map */ - SingleItemStorage textures(); + ItemStorage settings(); /** - * Returns a {@link SingleItemStorage} for the marker-data (live/markers.json) of this map + * Returns a {@link ItemStorage} for the texture-data (textures.json) of this map */ - SingleItemStorage markers(); + ItemStorage textures(); /** - * Returns a {@link SingleItemStorage} for the player-data (live/players.json) of this map + * Returns a {@link ItemStorage} for the marker-data (live/markers.json) of this map */ - SingleItemStorage players(); + ItemStorage markers(); + + /** + * Returns a {@link ItemStorage} for the player-data (live/players.json) of this map + */ + ItemStorage players(); /** * Deletes the entire map from the storage diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java index 274e78bd..f75cdca0 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java @@ -37,7 +37,10 @@ public interface Storage extends Closeable { void initialize() throws IOException; /** - * Returns the {@link MapStorage} for the given mapId + * Returns the {@link MapStorage} for the given mapId.
+ *
+ * If this method is invoked multiple times with the same mapId, it is important that the returned MapStorage should at least + * be equal (equals() == true) to the previously returned storages! */ MapStorage map(String mapId); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/compression/CompressedInputStream.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/compression/CompressedInputStream.java index c7055a2c..597d54a4 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/compression/CompressedInputStream.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/compression/CompressedInputStream.java @@ -36,6 +36,14 @@ public class CompressedInputStream extends DelegateInputStream { private final Compression compression; + /** + * Creates a new CompressedInputStream with {@link Compression#NONE} from an (uncompressed) {@link InputStream}. + * This does not compress the provided InputStream. + */ + public CompressedInputStream(InputStream in) { + this(in, Compression.NONE); + } + /** * Creates a new CompressedInputStream from an already compressed {@link InputStream} and the {@link Compression} * it is compressed with. diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/PathBasedGridStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileGridStorage.java similarity index 67% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/PathBasedGridStorage.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileGridStorage.java index e27d1deb..81703b55 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/PathBasedGridStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileGridStorage.java @@ -24,15 +24,17 @@ */ package de.bluecolored.bluemap.core.storage.file; +import de.bluecolored.bluemap.core.storage.GridStorage; +import de.bluecolored.bluemap.core.storage.ItemStorage; import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream; import de.bluecolored.bluemap.core.storage.compression.Compression; -import de.bluecolored.bluemap.core.storage.GridStorage; -import de.bluecolored.bluemap.core.storage.SingleItemStorage; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.LinkedList; import java.util.Objects; @@ -41,68 +43,72 @@ import java.util.stream.Stream; @RequiredArgsConstructor -class PathBasedGridStorage implements GridStorage { +class FileGridStorage implements GridStorage { private static final Pattern ITEM_PATH_PATTERN = Pattern.compile("x(-?\\d+)z(-?\\d+)"); - private final PathBasedMapStorage storage; private final Path root; private final String suffix; private final Compression compression; + private final boolean atomic; @Override public OutputStream write(int x, int z) throws IOException { - return item(x, z).write(); + return cell(x, z).write(); } @Override - public CompressedInputStream read(int x, int z) throws IOException { - return item(x, z).read(); + public @Nullable CompressedInputStream read(int x, int z) throws IOException { + return cell(x, z).read(); } @Override public void delete(int x, int z) throws IOException { - item(x, z).delete(); + cell(x, z).delete(); } @Override public boolean exists(int x, int z) throws IOException { - return item(x, z).exists(); + return cell(x, z).exists(); } + @Override + public ItemStorage cell(int x, int z) { + return new FileItemStorage(getItemPath(x, z), compression, atomic); + } + + @SuppressWarnings("resource") @Override public Stream stream() throws IOException { - return storage.files(root) + if (!Files.exists(root)) return Stream.empty(); + return Files.walk(root) + .filter(Files::isRegularFile) .map(itemPath -> { Path path = itemPath; if (!path.startsWith(root)) return null; path = root.relativize(path); String name = path.toString(); - name = name.replace(root.getFileSystem().getSeparator(), ""); if (!name.endsWith(suffix)) return null; - name = name.substring(name.length() - suffix.length()); + name = name.substring(0, name.length() - suffix.length()); + name = name.replace(root.getFileSystem().getSeparator(), ""); Matcher matcher = ITEM_PATH_PATTERN.matcher(name); if (!matcher.matches()) return null; int x = Integer.parseInt(matcher.group(1)); int z = Integer.parseInt(matcher.group(2)); - return new PathCell(x, z, itemPath); + return new PathCell(x, z, itemPath, compression, atomic); }) .filter(Objects::nonNull); } @Override public boolean isClosed() { - return storage.isClosed(); + return false; } - public SingleItemStorage item(int x, int z) { - return storage.file(root.resolve(getGridPath(x, z)), compression); - } - - public Path getGridPath(int x, int z) { + public Path getItemPath(int x, int z) { StringBuilder sb = new StringBuilder() .append('x') .append(x) @@ -111,59 +117,34 @@ public Path getGridPath(int x, int z) { LinkedList folders = new LinkedList<>(); StringBuilder folder = new StringBuilder(); - sb.chars().forEach(i -> { - char c = (char) i; + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); folder.append(c); if (c >= '0' && c <= '9') { folders.add(folder.toString()); folder.delete(0, folder.length()); } - }); + } String fileName = folders.removeLast(); folders.add(fileName + suffix); - return Path.of(folders.removeFirst(), folders.toArray(String[]::new)); + Path gridPath = root; + for (String part : folders) + gridPath = gridPath.resolve(part); + + return gridPath; } - @RequiredArgsConstructor - private class PathCell implements Cell { + private static class PathCell extends FileItemStorage implements Cell { @Getter private final int x, z; - private final Path path; - private SingleItemStorage storage; - - @Override - public OutputStream write() throws IOException { - return storage().write(); - } - - @Override - public CompressedInputStream read() throws IOException { - return storage().read(); - } - - @Override - public void delete() throws IOException { - storage().delete(); - } - - @Override - public boolean exists() throws IOException { - return storage().exists(); - } - - @Override - public boolean isClosed() { - return PathBasedGridStorage.this.isClosed(); - } - - private SingleItemStorage storage() { - if (storage == null) - storage = PathBasedGridStorage.this.storage.file(path, compression); - return storage; + public PathCell(int x, int z, Path itemPath, Compression compression, boolean atomic) { + super(itemPath, compression, atomic); + this.x = x; + this.z = z; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileItemStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileItemStorage.java new file mode 100644 index 00000000..1a51d76a --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileItemStorage.java @@ -0,0 +1,61 @@ +package de.bluecolored.bluemap.core.storage.file; + +import de.bluecolored.bluemap.core.storage.ItemStorage; +import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream; +import de.bluecolored.bluemap.core.storage.compression.Compression; +import de.bluecolored.bluemap.core.util.FileHelper; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Nullable; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +@RequiredArgsConstructor +public class FileItemStorage implements ItemStorage { + + private final Path file; + private final Compression compression; + private final boolean atomic; + + @Override + public OutputStream write() throws IOException { + if (atomic) + return compression.compress(FileHelper.createFilepartOutputStream(file)); + + Path folder = file.toAbsolutePath().normalize().getParent(); + FileHelper.createDirectories(folder); + return compression.compress(Files.newOutputStream(file, + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)); + } + + @Override + public @Nullable CompressedInputStream read() throws IOException { + if (!Files.exists(file)) return null; + try { + return new CompressedInputStream(Files.newInputStream(file), compression); + } catch (FileNotFoundException | NoSuchFileException ex) { + return null; + } + } + + @Override + public void delete() throws IOException { + if (Files.exists(file)) Files.delete(file); + } + + @Override + public boolean exists() { + return Files.exists(file); + } + + @Override + public boolean isClosed() { + return false; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileMapStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileMapStorage.java index 95b00990..d909e836 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileMapStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileMapStorage.java @@ -24,49 +24,126 @@ */ package de.bluecolored.bluemap.core.storage.file; -import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import de.bluecolored.bluemap.core.storage.GridStorage; +import de.bluecolored.bluemap.core.storage.ItemStorage; +import de.bluecolored.bluemap.core.storage.MapStorage; import de.bluecolored.bluemap.core.storage.compression.Compression; -import de.bluecolored.bluemap.core.storage.SingleItemStorage; import de.bluecolored.bluemap.core.util.DeletingPathVisitor; -import de.bluecolored.bluemap.core.util.FileHelper; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import java.io.FileNotFoundException; import java.io.IOException; -import java.io.OutputStream; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.LinkedList; import java.util.function.DoublePredicate; import java.util.stream.Collectors; import java.util.stream.Stream; -@Getter -public class FileMapStorage extends PathBasedMapStorage { +public class FileMapStorage implements MapStorage { + + private static final String TILES_PATH = "tiles"; + private static final String RENDER_STATE_PATH = "rstate"; + private static final String LIVE_PATH = "live"; private final Path root; + private final Compression compression; + private final boolean atomic; - public FileMapStorage(Path root, Compression compression) { - super( - compression, - ".prbm", - ".png" - ); + private final GridStorage hiresGridStorage; + private final LoadingCache lowresGridStorages; + private final GridStorage tileStateStorage; + private final GridStorage chunkStateStorage; + + public FileMapStorage(Path root, Compression compression, boolean atomic) { this.root = root; + this.compression = compression; + this.atomic = atomic; + + this.hiresGridStorage = new FileGridStorage( + root.resolve(TILES_PATH).resolve("0"), + ".prbm" + compression.getFileSuffix(), + compression, + atomic + ); + + this.lowresGridStorages = Caffeine.newBuilder().build(lod -> new FileGridStorage( + root.resolve(TILES_PATH).resolve(String.valueOf(lod)), + ".png", + Compression.NONE, + atomic + )); + + this.tileStateStorage = new FileGridStorage( + root.resolve(RENDER_STATE_PATH), + ".tiles.dat", + Compression.GZIP, + atomic + ); + + this.chunkStateStorage = new FileGridStorage( + root.resolve(RENDER_STATE_PATH).resolve(""), + ".chunks.dat", + Compression.GZIP, + atomic + ); + } @Override - public SingleItemStorage file(Path file, Compression compression) { - return new FileItemStorage(root.resolve(file), compression); + public GridStorage hiresTiles() { + return hiresGridStorage; } @Override - @SuppressWarnings("resource") - public Stream files(Path path) throws IOException { - return Files.walk(root.resolve(path)) - .filter(Files::isRegularFile); + public GridStorage lowresTiles(int lod) { + return lowresGridStorages.get(lod); + } + + @Override + public GridStorage tileState() { + return tileStateStorage; + } + + @Override + public GridStorage chunkState() { + return chunkStateStorage; + } + + public Path getAssetPath(String name) { + String[] parts = MapStorage.escapeAssetName(name) + .split("/"); + + Path assetPath = root.resolve("assets"); + for (String part : parts) + assetPath = assetPath.resolve(part); + + return assetPath; + } + + @Override + public ItemStorage asset(String name) { + return new FileItemStorage(getAssetPath(name), Compression.NONE, atomic); + } + + @Override + public ItemStorage settings() { + return new FileItemStorage(root.resolve("settings.json"), Compression.NONE, atomic); + } + + @Override + public ItemStorage textures() { + return new FileItemStorage(root.resolve("textures.json" + compression.getFileSuffix()), compression, atomic); + } + + @Override + public ItemStorage markers() { + return new FileItemStorage(root.resolve(LIVE_PATH).resolve("markers.json"), Compression.NONE, atomic); + } + + @Override + public ItemStorage players() { + return new FileItemStorage(root.resolve(LIVE_PATH).resolve("players.json"), Compression.NONE, atomic); } @Override @@ -107,42 +184,4 @@ public boolean isClosed() { return false; } - @RequiredArgsConstructor - private static class FileItemStorage implements SingleItemStorage { - - private final Path file; - private final Compression compression; - - @Override - public OutputStream write() throws IOException { - return compression.compress(FileHelper.createFilepartOutputStream(file)); - } - - @Override - public CompressedInputStream read() throws IOException { - if (!Files.exists(file)) return null; - try { - return new CompressedInputStream(Files.newInputStream(file), compression); - } catch (FileNotFoundException | NoSuchFileException ex) { - return null; - } - } - - @Override - public void delete() throws IOException { - Files.delete(file); - } - - @Override - public boolean exists() { - return Files.exists(file); - } - - @Override - public boolean isClosed() { - return false; - } - - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java index b23e6ca1..97aa487f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/FileStorage.java @@ -39,11 +39,11 @@ public class FileStorage implements Storage { private final Path root; private final LoadingCache mapStorages; - public FileStorage(Path root, Compression compression) { + public FileStorage(Path root, Compression compression, boolean atomic) { this.root = root; mapStorages = Caffeine.newBuilder() - .build(id -> new FileMapStorage(root.resolve(id), compression)); + .build(id -> new FileMapStorage(root.resolve(id), compression, atomic)); } @Override diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/PathBasedMapStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/PathBasedMapStorage.java deleted file mode 100644 index 25001a25..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/file/PathBasedMapStorage.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.core.storage.file; - -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import de.bluecolored.bluemap.core.storage.compression.Compression; -import de.bluecolored.bluemap.core.storage.GridStorage; -import de.bluecolored.bluemap.core.storage.MapStorage; -import de.bluecolored.bluemap.core.storage.SingleItemStorage; - -import java.io.IOException; -import java.nio.file.FileVisitOption; -import java.nio.file.Path; -import java.util.stream.Stream; - -public abstract class PathBasedMapStorage implements MapStorage { - - public static final Path SETTINGS_PATH = Path.of("settings.json"); - public static final Path TEXTURES_PATH = Path.of("textures.json"); - public static final Path RENDER_STATE_PATH = Path.of(".rstate"); - public static final Path MARKERS_PATH = Path.of("live", "markers.json"); - public static final Path PLAYERS_PATH = Path.of("live", "players.json"); - - private final GridStorage hiresGridStorage; - private final LoadingCache lowresGridStorages; - - public PathBasedMapStorage(Compression compression, String hiresSuffix, String lowresSuffix) { - this.hiresGridStorage = new PathBasedGridStorage( - this, - Path.of("tiles", "0"), - hiresSuffix + compression.getFileSuffix(), - compression - ); - - this.lowresGridStorages = Caffeine.newBuilder().build(lod -> new PathBasedGridStorage( - this, - Path.of("tiles", String.valueOf(lod)), - lowresSuffix, - Compression.NONE - )); - } - - @Override - public GridStorage hiresTiles() { - return hiresGridStorage; - } - - @Override - public GridStorage lowresTiles(int lod) { - return lowresGridStorages.get(lod); - } - - public Path getAssetPath(String name) { - String[] parts = MapStorage.escapeAssetName(name) - .split("/"); - return Path.of("assets", parts); - } - - @Override - public SingleItemStorage asset(String name) { - return file(getAssetPath(name), Compression.NONE); - } - - @Override - public SingleItemStorage renderState() { - return file(RENDER_STATE_PATH, Compression.NONE); - } - - @Override - public SingleItemStorage settings() { - return file(SETTINGS_PATH, Compression.NONE); - } - - @Override - public SingleItemStorage textures() { - return file(TEXTURES_PATH, Compression.NONE); - } - - @Override - public SingleItemStorage markers() { - return file(MARKERS_PATH, Compression.NONE); - } - - @Override - public SingleItemStorage players() { - return file(PLAYERS_PATH, Compression.NONE); - } - - /** - * Returns a {@link SingleItemStorage} for a file with the given path and compression. - * The file does not have to actually exist. - */ - public abstract SingleItemStorage file(Path file, Compression compression); - - /** - * Returns a stream with all file-paths of existing files at or below the given path. - * (Including files in potential sub-folders)
- * Basically, this method should mimic the functionality of - * {@link java.nio.file.Files#walk(Path, FileVisitOption...)} - */ - public abstract Stream files(Path path) throws IOException; - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/Database.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/Database.java index 50c7e1d4..c1bcb1a8 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/Database.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/Database.java @@ -78,6 +78,7 @@ public R run(ConnectionFunction action) throws IOException { SQLException sqlException = null; try { + // try the action 2 times if a "recoverable" exception is thrown for (int i = 0; i < 2; i++) { try (Connection connection = dataSource.getConnection()) { try { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLTileStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLGridStorage.java similarity index 81% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLTileStorage.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLGridStorage.java index dc019499..6c527ff7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLTileStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLGridStorage.java @@ -24,10 +24,12 @@ */ package de.bluecolored.bluemap.core.storage.sql; +import de.bluecolored.bluemap.core.storage.ItemStorage; import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream; import de.bluecolored.bluemap.core.storage.compression.Compression; import de.bluecolored.bluemap.core.storage.GridStorage; import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet; +import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.Nullable; @@ -37,36 +39,41 @@ import java.util.stream.StreamSupport; @RequiredArgsConstructor -public class SQLTileStorage implements GridStorage { +public class SQLGridStorage implements GridStorage { private final CommandSet sql; - private final String mapId; - private final int lod; + private final String map; + private final Key storage; private final Compression compression; @Override public OutputStream write(int x, int z) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); return new OnCloseOutputStream(compression.compress(bytes), - () -> sql.writeMapTile(mapId, lod, x, z, compression, bytes.toByteArray()) + () -> sql.writeGridItem(map, storage, x, z, compression, bytes.toByteArray()) ); } @Override public @Nullable CompressedInputStream read(int x, int z) throws IOException { - byte[] data = sql.readMapTile(mapId, lod, x, z, compression); + byte[] data = sql.readGridItem(map, storage, x, z, compression); if (data == null) return null; return new CompressedInputStream(new ByteArrayInputStream(data), compression); } @Override public void delete(int x, int z) throws IOException { - sql.deleteMapTile(mapId, lod, x, z, compression); + sql.deleteGridItem(map, storage, x, z); } @Override public boolean exists(int x, int z) throws IOException { - return sql.hasMapTile(mapId, lod, x, z, compression); + return sql.hasGridItem(map, storage, x, z, compression); + } + + @Override + public ItemStorage cell(int x, int z) { + return new GridStorageCell(this, x, z); } @Override @@ -74,7 +81,7 @@ public Stream stream() throws IOException { return StreamSupport.stream( new PageSpliterator<>(page -> { try { - return sql.listMapTiles(mapId, lod, compression, page * 1000, 1000); + return sql.listGridItems(map, storage, compression, page * 1000, 1000); } catch (IOException ex) { throw new RuntimeException(ex); } }), false diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLMetaItemStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLItemStorage.java similarity index 84% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLMetaItemStorage.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLItemStorage.java index 99075f1f..0881470b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLMetaItemStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLItemStorage.java @@ -26,8 +26,9 @@ import de.bluecolored.bluemap.core.storage.compression.CompressedInputStream; import de.bluecolored.bluemap.core.storage.compression.Compression; -import de.bluecolored.bluemap.core.storage.SingleItemStorage; +import de.bluecolored.bluemap.core.storage.ItemStorage; import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet; +import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.util.stream.OnCloseOutputStream; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.Nullable; @@ -35,36 +36,36 @@ import java.io.*; @RequiredArgsConstructor -public class SQLMetaItemStorage implements SingleItemStorage { +public class SQLItemStorage implements ItemStorage { private final CommandSet sql; - private final String mapId; - private final String itemName; + private final String map; + private final Key storage; private final Compression compression; @Override public OutputStream write() throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); return new OnCloseOutputStream(compression.compress(bytes), - () -> sql.writeMapMeta(mapId, itemName, bytes.toByteArray()) + () -> sql.writeItem(map, storage, compression, bytes.toByteArray()) ); } @Override public @Nullable CompressedInputStream read() throws IOException { - byte[] data = sql.readMapMeta(mapId, itemName); + byte[] data = sql.readItem(map, storage, compression); if (data == null) return null; return new CompressedInputStream(new ByteArrayInputStream(data), compression); } @Override public void delete() throws IOException { - sql.deleteMapMeta(mapId, itemName); + sql.deleteItem(map, storage); } @Override public boolean exists() throws IOException { - return sql.hasMapMeta(mapId, itemName); + return sql.hasItem(map, storage, compression); } @Override diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLMapStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLMapStorage.java index d55abebf..0f3377fc 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLMapStorage.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/SQLMapStorage.java @@ -24,116 +24,53 @@ */ package de.bluecolored.bluemap.core.storage.sql; +import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; import de.bluecolored.bluemap.core.storage.GridStorage; -import de.bluecolored.bluemap.core.storage.MapStorage; -import de.bluecolored.bluemap.core.storage.SingleItemStorage; +import de.bluecolored.bluemap.core.storage.ItemStorage; +import de.bluecolored.bluemap.core.storage.KeyedMapStorage; import de.bluecolored.bluemap.core.storage.compression.Compression; import de.bluecolored.bluemap.core.storage.sql.commandset.CommandSet; +import de.bluecolored.bluemap.core.util.Key; import java.io.IOException; import java.util.function.DoublePredicate; -public class SQLMapStorage implements MapStorage { - - public static final String SETTINGS_META_NAME = "settings.json"; - public static final String TEXTURES_META_NAME = "textures.json"; - public static final String RENDER_STATE_META_NAME = ".rstate"; - public static final String MARKERS_META_NAME = "live/markers.json"; - public static final String PLAYERS_META_NAME = "live/players.json"; +public class SQLMapStorage extends KeyedMapStorage { private final String mapId; private final CommandSet sql; - private final SQLTileStorage hiresTileStorage; - private final LoadingCache lowresGridStorages; - - private final SingleItemStorage renderStateStorage; - private final SingleItemStorage settingsStorage; - private final SingleItemStorage texturesStorage; - private final SingleItemStorage markersStorage; - private final SingleItemStorage playersStorage; + private final Cache itemStorages = Caffeine.newBuilder().build(); + private final Cache gridStorages = Caffeine.newBuilder().build(); public SQLMapStorage(String mapId, CommandSet sql, Compression compression) { + super(compression); + this.mapId = mapId; this.sql = sql; - - this.hiresTileStorage = new SQLTileStorage( - sql, - mapId, - 0, - compression - ); - - this.lowresGridStorages = Caffeine.newBuilder().build(lod -> new SQLTileStorage( - sql, - mapId, - lod, - Compression.NONE - )); - - renderStateStorage = meta(RENDER_STATE_META_NAME, Compression.NONE); - settingsStorage = meta(SETTINGS_META_NAME, Compression.NONE); - texturesStorage = meta(TEXTURES_META_NAME, Compression.NONE); - markersStorage = meta(MARKERS_META_NAME, Compression.NONE); - playersStorage = meta(PLAYERS_META_NAME, Compression.NONE); } @Override - public GridStorage hiresTiles() { - return hiresTileStorage; + public ItemStorage item(Key key, Compression compression) { + return itemStorages.get(key, k -> new SQLItemStorage(sql, mapId, key, compression)); } @Override - public GridStorage lowresTiles(int lod) { - return lowresGridStorages.get(lod); - } - - public String getAssetMetaName(String assetName) { - return "assets/" + MapStorage.escapeAssetName(assetName); - } - - @Override - public SingleItemStorage asset(String name) { - return meta(getAssetMetaName(name), Compression.NONE); - } - - @Override - public SingleItemStorage renderState() { - return renderStateStorage; - } - - @Override - public SingleItemStorage settings() { - return settingsStorage; - } - - @Override - public SingleItemStorage textures() { - return texturesStorage; - } - - @Override - public SingleItemStorage markers() { - return markersStorage; - } - - @Override - public SingleItemStorage players() { - return playersStorage; + public GridStorage grid(Key key, Compression compression) { + return gridStorages.get(key, k -> new SQLGridStorage(sql, mapId, key, compression)); } @Override public void delete(DoublePredicate onProgress) throws IOException { // delete tiles in 1000er steps to track progress - int tileCount = sql.countAllMapTiles(mapId); + int tileCount = sql.countMapGridsItems(mapId); if (tileCount > 0) { int totalDeleted = 0; int deleted = 0; do { - deleted = sql.purgeMapTiles(mapId, 1000); + deleted = sql.purgeMapGrids(mapId, 1000); totalDeleted += deleted; if (!onProgress.test((double) totalDeleted / tileCount)) @@ -152,10 +89,6 @@ public boolean exists() throws IOException { return sql.hasMap(mapId); } - private SingleItemStorage meta(String name, Compression compression) { - return new SQLMetaItemStorage(sql, mapId, name, compression); - } - @Override public boolean isClosed() { return sql.isClosed(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/AbstractCommandSet.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/AbstractCommandSet.java index 43b4de11..e5f71cf6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/AbstractCommandSet.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/AbstractCommandSet.java @@ -28,6 +28,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import de.bluecolored.bluemap.core.storage.compression.Compression; import de.bluecolored.bluemap.core.storage.sql.Database; +import de.bluecolored.bluemap.core.util.Key; import lombok.RequiredArgsConstructor; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.Nullable; @@ -41,12 +42,16 @@ @RequiredArgsConstructor public abstract class AbstractCommandSet implements CommandSet { - private final Database db; + protected final Database db; - final LoadingCache mapKeys = Caffeine.newBuilder() + protected final LoadingCache mapKeys = Caffeine.newBuilder() .build(this::findOrCreateMapKey); - final LoadingCache compressionKeys = Caffeine.newBuilder() + protected final LoadingCache compressionKeys = Caffeine.newBuilder() .build(this::findOrCreateCompressionKey); + protected final LoadingCache itemStorageKeys = Caffeine.newBuilder() + .build(this::findOrCreateItemStorageKey); + protected final LoadingCache gridStorageKeys = Caffeine.newBuilder() + .build(this::findOrCreateGridStorageKey); @Language("sql") public abstract String createMapTableStatement(); @@ -55,53 +60,55 @@ public abstract class AbstractCommandSet implements CommandSet { public abstract String createCompressionTableStatement(); @Language("sql") - public abstract String createMapMetaTableStatement(); + public abstract String createItemStorageTableStatement(); @Language("sql") - public abstract String createMapTileTableStatement(); + public abstract String createItemStorageDataTableStatement(); @Language("sql") - public abstract String fixLegacyCompressionIdsStatement(); + public abstract String createGridStorageTableStatement(); + + @Language("sql") + public abstract String createGridStorageDataTableStatement(); public void initializeTables() throws IOException { db.run(connection -> { executeUpdate(connection, createMapTableStatement()); executeUpdate(connection, createCompressionTableStatement()); - executeUpdate(connection, createMapMetaTableStatement()); - executeUpdate(connection, createMapTileTableStatement()); + executeUpdate(connection, createItemStorageTableStatement()); + executeUpdate(connection, createItemStorageDataTableStatement()); + executeUpdate(connection, createGridStorageTableStatement()); + executeUpdate(connection, createGridStorageDataTableStatement()); }); - - db.run(connection -> executeUpdate(connection, fixLegacyCompressionIdsStatement())); } @Language("sql") - public abstract String writeMapTileStatement(); + public abstract String itemStorageWriteStatement(); @Override - public int writeMapTile( - String mapId, int lod, int x, int z, Compression compression, - byte[] bytes - ) throws IOException { + public void writeItem(String mapId, Key key, Compression compression, byte[] bytes) throws IOException { int mapKey = mapKey(mapId); + int storageKey = itemStorageKey(key); + int compressionKey = compressionKey(compression); + db.run(connection -> executeUpdate(connection, + itemStorageWriteStatement(), + mapKey, storageKey, compressionKey, + bytes + )); + } + + @Language("sql") + public abstract String itemStorageReadStatement(); + + @Override + public byte @Nullable [] readItem(String mapId, Key key, Compression compression) throws IOException { + int mapKey = mapKey(mapId); + int storageKey = itemStorageKey(key); int compressionKey = compressionKey(compression); - return db.run(connection -> { - return executeUpdate(connection, - writeMapTileStatement(), - mapKey, lod, x, z, compressionKey, - bytes - ); - }); - } - - @Language("sql") - public abstract String readMapTileStatement(); - - @Override - public byte @Nullable [] readMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException { return db.run(connection -> { ResultSet result = executeQuery(connection, - readMapTileStatement(), - mapId, lod, x, z, compression.getKey().getFormatted() + itemStorageReadStatement(), + mapKey, storageKey, compressionKey ); if (!result.next()) return null; return result.getBytes(1); @@ -109,29 +116,30 @@ public int writeMapTile( } @Language("sql") - public abstract String deleteMapTileStatement(); + public abstract String itemStorageDeleteStatement(); @Override - public int deleteMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException { + public void deleteItem(String mapId, Key key) throws IOException { int mapKey = mapKey(mapId); - int compressionKey = compressionKey(compression); - return db.run(connection -> { - return executeUpdate(connection, - deleteMapTileStatement(), - mapKey, lod, x, z, compressionKey - ); - }); + int storageKey = itemStorageKey(key); + db.run(connection -> executeUpdate(connection, + itemStorageDeleteStatement(), + mapKey, storageKey + )); } @Language("sql") - public abstract String hasMapTileStatement(); + public abstract String itemStorageHasStatement(); @Override - public boolean hasMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException { + public boolean hasItem(String mapId, Key key, Compression compression) throws IOException { + int mapKey = mapKey(mapId); + int storageKey = itemStorageKey(key); + int compressionKey = compressionKey(compression); return db.run(connection -> { ResultSet result = executeQuery(connection, - hasMapTileStatement(), - mapId, lod, x, z, compression.getKey().getFormatted() + itemStorageHasStatement(), + mapKey, storageKey, compressionKey ); if (!result.next()) throw new IllegalStateException("Counting query returned empty result!"); return result.getBoolean(1); @@ -139,43 +147,93 @@ public boolean hasMapTile(String mapId, int lod, int x, int z, Compression compr } @Language("sql") - public abstract String countAllMapTilesStatement(); + public abstract String gridStorageWriteStatement(); @Override - public int countAllMapTiles(String mapId) throws IOException { + public void writeGridItem( + String mapId, Key key, int x, int z, Compression compression, + byte[] bytes + ) throws IOException { + int mapKey = mapKey(mapId); + int storageKey = gridStorageKey(key); + int compressionKey = compressionKey(compression); + db.run(connection -> executeUpdate(connection, + gridStorageWriteStatement(), + mapKey, storageKey, x, z, compressionKey, + bytes + )); + } + + @Language("sql") + public abstract String gridStorageReadStatement(); + + @Override + public byte @Nullable [] readGridItem( + String mapId, Key key, int x, int z, Compression compression + ) throws IOException { + int mapKey = mapKey(mapId); + int storageKey = gridStorageKey(key); + int compressionKey = compressionKey(compression); return db.run(connection -> { ResultSet result = executeQuery(connection, - countAllMapTilesStatement(), - mapId + gridStorageReadStatement(), + mapKey, storageKey, x, z, compressionKey + ); + if (!result.next()) return null; + return result.getBytes(1); + }); + } + + @Language("sql") + public abstract String gridStorageDeleteStatement(); + + @Override + public void deleteGridItem( + String mapId, Key key, int x, int z + ) throws IOException { + int mapKey = mapKey(mapId); + int storageKey = gridStorageKey(key); + db.run(connection -> executeUpdate(connection, + gridStorageDeleteStatement(), + mapKey, storageKey, x, z + )); + } + + @Language("sql") + public abstract String gridStorageHasStatement(); + + @Override + public boolean hasGridItem( + String mapId, Key key, int x, int z, Compression compression + ) throws IOException { + int mapKey = mapKey(mapId); + int storageKey = gridStorageKey(key); + int compressionKey = compressionKey(compression); + return db.run(connection -> { + ResultSet result = executeQuery(connection, + gridStorageHasStatement(), + mapKey, storageKey, x, z, compressionKey ); if (!result.next()) throw new IllegalStateException("Counting query returned empty result!"); - return result.getInt(1); + return result.getBoolean(1); }); } @Language("sql") - public abstract String purgeMapTilesStatement(); + public abstract String gridStorageListStatement(); @Override - public int purgeMapTiles(String mapId, int limit) throws IOException { + public TilePosition[] listGridItems( + String mapId, Key key, Compression compression, + int start, int count + ) throws IOException { int mapKey = mapKey(mapId); - return db.run(connection -> { - return executeUpdate(connection, - purgeMapTilesStatement(), - mapKey, limit - ); - }); - } - - @Language("sql") - public abstract String listMapTilesStatement(); - - @Override - public TilePosition[] listMapTiles(String mapId, int lod, Compression compression, int start, int count) throws IOException { + int storageKey = gridStorageKey(key); + int compressionKey = compressionKey(compression); return db.run(connection -> { ResultSet result = executeQuery(connection, - listMapTilesStatement(), - mapId, lod, compression.getKey().getFormatted(), + gridStorageListStatement(), + mapKey, storageKey, compressionKey, count, start ); @@ -199,96 +257,46 @@ public TilePosition[] listMapTiles(String mapId, int lod, Compression compressio } @Language("sql") - public abstract String writeMapMetaStatement(); + public abstract String gridStorageCountMapItemsStatement(); @Override - public int writeMapMeta(String mapId, String itemName, byte[] bytes) throws IOException { + public int countMapGridsItems(String mapId) throws IOException { int mapKey = mapKey(mapId); - return db.run(connection -> { - return executeUpdate(connection, - writeMapMetaStatement(), - mapKey, itemName, - bytes - ); - }); - } - - @Language("sql") - public abstract String readMapMetaStatement(); - - @Override - public byte @Nullable [] readMapMeta(String mapId, String itemName) throws IOException { return db.run(connection -> { ResultSet result = executeQuery(connection, - readMapMetaStatement(), - mapId, itemName - ); - if (!result.next()) return null; - return result.getBytes(1); - }); - } - - @Language("sql") - public abstract String deleteMapMetaStatement(); - - @Override - public int deleteMapMeta(String mapId, String itemName) throws IOException { - int mapKey = mapKey(mapId); - return db.run(connection -> { - return executeUpdate(connection, - deleteMapMetaStatement(), - mapKey, itemName - ); - }); - } - - @Language("sql") - public abstract String hasMapMetaStatement(); - - @Override - public boolean hasMapMeta(String mapId, String itemName) throws IOException { - return db.run(connection -> { - ResultSet result = executeQuery(connection, - hasMapMetaStatement(), - mapId, itemName + gridStorageCountMapItemsStatement(), + mapKey ); if (!result.next()) throw new IllegalStateException("Counting query returned empty result!"); - return result.getBoolean(1); + return result.getInt(1); }); } @Language("sql") - public abstract String purgeMapTileTableStatement(); + public abstract String gridStoragePurgeMapStatement(); + + @Override + public int purgeMapGrids(String mapId, int limit) throws IOException { + int mapKey = mapKey(mapId); + return db.run(connection -> { + return executeUpdate(connection, + gridStoragePurgeMapStatement(), + mapKey, limit + ); + }); + } @Language("sql") - public abstract String purgeMapMetaTableStatement(); - - @Language("sql") - public abstract String deleteMapStatement(); + public abstract String purgeMapStatement(); @Override public void purgeMap(String mapId) throws IOException { synchronized (mapKeys) { int mapKey = mapKey(mapId); - db.run(connection -> { - - executeUpdate(connection, - purgeMapTileTableStatement(), - mapKey - ); - - executeUpdate(connection, - purgeMapMetaTableStatement(), - mapKey - ); - - executeUpdate(connection, - deleteMapStatement(), - mapKey - ); - - }); - + db.run(connection -> executeUpdate(connection, + purgeMapStatement(), + mapKey + )); mapKeys.invalidate(mapId); } } @@ -398,6 +406,78 @@ public int findOrCreateCompressionKey(Compression compression) throws IOExceptio }); } + @Language("sql") + public abstract String findItemStorageKeyStatement(); + + @Language("sql") + public abstract String createItemStorageKeyStatement(); + + public int itemStorageKey(Key key) { + synchronized (itemStorageKeys) { + //noinspection DataFlowIssue + return itemStorageKeys.get(key); + } + } + + public int findOrCreateItemStorageKey(Key key) throws IOException { + return db.run(connection -> { + ResultSet result = executeQuery(connection, + findItemStorageKeyStatement(), + key.getFormatted() + ); + + if (result.next()) + return result.getInt(1); + + PreparedStatement statement = connection.prepareStatement( + createItemStorageKeyStatement(), + Statement.RETURN_GENERATED_KEYS + ); + statement.setString(1, key.getFormatted()); + statement.executeUpdate(); + + ResultSet keys = statement.getGeneratedKeys(); + if (!keys.next()) throw new IllegalStateException("No generated key returned!"); + return keys.getInt(1); + }); + } + + @Language("sql") + public abstract String findGridStorageKeyStatement(); + + @Language("sql") + public abstract String createGridStorageKeyStatement(); + + public int gridStorageKey(Key key) { + synchronized (gridStorageKeys) { + //noinspection DataFlowIssue + return gridStorageKeys.get(key); + } + } + + public int findOrCreateGridStorageKey(Key key) throws IOException { + return db.run(connection -> { + ResultSet result = executeQuery(connection, + findGridStorageKeyStatement(), + key.getFormatted() + ); + + if (result.next()) + return result.getInt(1); + + PreparedStatement statement = connection.prepareStatement( + createGridStorageKeyStatement(), + Statement.RETURN_GENERATED_KEYS + ); + statement.setString(1, key.getFormatted()); + statement.executeUpdate(); + + ResultSet keys = statement.getGeneratedKeys(); + if (!keys.next()) throw new IllegalStateException("No generated key returned!"); + return keys.getInt(1); + }); + } + @Override public boolean isClosed() { return db.isClosed(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/CommandSet.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/CommandSet.java index 2ebc1490..f1e00cab 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/CommandSet.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/CommandSet.java @@ -25,6 +25,7 @@ package de.bluecolored.bluemap.core.storage.sql.commandset; import de.bluecolored.bluemap.core.storage.compression.Compression; +import de.bluecolored.bluemap.core.util.Key; import org.jetbrains.annotations.Nullable; import java.io.Closeable; @@ -34,39 +35,39 @@ public interface CommandSet extends Closeable { void initializeTables() throws IOException; - int writeMapTile( - String mapId, int lod, int x, int z, Compression compression, + void writeItem(String mapId, Key key, Compression compression, byte[] bytes) throws IOException; + + byte @Nullable [] readItem(String mapId, Key key, Compression compression) throws IOException; + + void deleteItem(String mapId, Key key) throws IOException; + + boolean hasItem(String mapId, Key key, Compression compression) throws IOException; + + void writeGridItem( + String mapId, Key key, int x, int z, Compression compression, byte[] bytes ) throws IOException; - byte @Nullable [] readMapTile( - String mapId, int lod, int x, int z, Compression compression + byte @Nullable [] readGridItem( + String mapId, Key key, int x, int z, Compression compression ) throws IOException; - int deleteMapTile( - String mapId, int lod, int x, int z, Compression compression + void deleteGridItem( + String mapId, Key key, int x, int z ) throws IOException; - boolean hasMapTile( - String mapId, int lod, int x, int z, Compression compression + boolean hasGridItem( + String mapId, Key key, int x, int z, Compression compression ) throws IOException; - TilePosition[] listMapTiles( - String mapId, int lod, Compression compression, + TilePosition[] listGridItems( + String mapId, Key key, Compression compression, int start, int count ) throws IOException; - int countAllMapTiles(String mapId) throws IOException; + int countMapGridsItems(String mapId) throws IOException; - int purgeMapTiles(String mapId, int limit) throws IOException; - - int writeMapMeta(String mapId, String itemName, byte[] bytes) throws IOException; - - byte @Nullable [] readMapMeta(String mapId, String itemName) throws IOException; - - int deleteMapMeta(String mapId, String itemName) throws IOException; - - boolean hasMapMeta(String mapId, String itemName) throws IOException; + int purgeMapGrids(String mapId, int limit) throws IOException; void purgeMap(String mapId) throws IOException; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/MySQLCommandSet.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/MySQLCommandSet.java index b04db4f7..03d4410a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/MySQLCommandSet.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/MySQLCommandSet.java @@ -50,53 +50,51 @@ PRIMARY KEY (`id`), @Language("mysql") public String createCompressionTableStatement() { return """ - CREATE TABLE IF NOT EXISTS `bluemap_map_tile_compression` ( + CREATE TABLE IF NOT EXISTS `bluemap_compression` ( `id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, - `compression` VARCHAR(190) NOT NULL, + `key` VARCHAR(190) NOT NULL, PRIMARY KEY (`id`), - UNIQUE INDEX `compression` (`compression`) + UNIQUE INDEX `key` (`key`) ) COLLATE 'utf8mb4_bin' """; } @Override @Language("mysql") - public String createMapMetaTableStatement() { + public String createItemStorageTableStatement() { return """ - CREATE TABLE IF NOT EXISTS `bluemap_map_meta` ( - `map` SMALLINT UNSIGNED NOT NULL, - `key` varchar(190) NOT NULL, - `value` LONGBLOB NOT NULL, - PRIMARY KEY (`map`, `key`), - CONSTRAINT `fk_bluemap_map_meta_map` - FOREIGN KEY (`map`) - REFERENCES `bluemap_map` (`id`) - ON UPDATE RESTRICT - ON DELETE CASCADE + CREATE TABLE IF NOT EXISTS `bluemap_item_storage` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `key` VARCHAR(190) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `key` (`key`) ) COLLATE 'utf8mb4_bin' """; } @Override @Language("mysql") - public String createMapTileTableStatement() { + public String createItemStorageDataTableStatement() { return """ - CREATE TABLE IF NOT EXISTS `bluemap_map_tile` ( + CREATE TABLE IF NOT EXISTS `bluemap_item_storage_data` ( `map` SMALLINT UNSIGNED NOT NULL, - `lod` SMALLINT UNSIGNED NOT NULL, - `x` INT NOT NULL, - `z` INT NOT NULL, + `storage` INT UNSIGNED NOT NULL, `compression` SMALLINT UNSIGNED NOT NULL, `data` LONGBLOB NOT NULL, - PRIMARY KEY (`map`, `lod`, `x`, `z`), - CONSTRAINT `fk_bluemap_map_tile_map` + PRIMARY KEY (`map`, `storage`), + CONSTRAINT `fk_bluemap_item_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, - CONSTRAINT `fk_bluemap_map_tile_compression` + CONSTRAINT `fk_bluemap_item` + FOREIGN KEY (`storage`) + REFERENCES `bluemap_item_storage` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_bluemap_item_compression` FOREIGN KEY (`compression`) - REFERENCES `bluemap_map_tile_compression` (`id`) + REFERENCES `bluemap_compression` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) COLLATE 'utf8mb4_bin' @@ -105,50 +103,112 @@ FOREIGN KEY (`compression`) @Override @Language("mysql") - public String fixLegacyCompressionIdsStatement() { + public String createGridStorageTableStatement() { return """ - UPDATE IGNORE `bluemap_map_tile_compression` - SET `compression` = CONCAT('bluemap:', `compression`) - WHERE NOT `compression` LIKE '%:%' + CREATE TABLE IF NOT EXISTS `bluemap_grid_storage` ( + `id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, + `key` VARCHAR(190) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `key` (`key`) + ) COLLATE 'utf8mb4_bin' """; } @Override @Language("mysql") - public String writeMapTileStatement() { + public String createGridStorageDataTableStatement() { + return """ + CREATE TABLE IF NOT EXISTS `bluemap_grid_storage_data` ( + `map` SMALLINT UNSIGNED NOT NULL, + `storage` SMALLINT UNSIGNED NOT NULL, + `x` INT NOT NULL, + `z` INT NOT NULL, + `compression` SMALLINT UNSIGNED NOT NULL, + `data` LONGBLOB NOT NULL, + PRIMARY KEY (`map`, `storage`, `x`, `z`), + CONSTRAINT `fk_bluemap_grid_map` + FOREIGN KEY (`map`) + REFERENCES `bluemap_map` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_bluemap_grid` + FOREIGN KEY (`storage`) + REFERENCES `bluemap_grid_storage` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_bluemap_grid_compression` + FOREIGN KEY (`compression`) + REFERENCES `bluemap_compression` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE + ) COLLATE 'utf8mb4_bin' + """; + } + + @Override + @Language("mysql") + public String itemStorageWriteStatement() { return """ REPLACE - INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) + INTO `bluemap_item_storage_data` (`map`, `storage`, `compression`, `data`) + VALUES (?, ?, ?, ?) + """; + } + + @Override + @Language("mysql") + public String itemStorageReadStatement() { + return """ + SELECT `data` + FROM `bluemap_item_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `compression` = ? + """; + } + + @Override + @Language("mysql") + public String itemStorageDeleteStatement() { + return """ + DELETE + FROM `bluemap_item_storage_data` + WHERE `map` = ? + AND `storage` = ? + """; + } + + @Override + @Language("mysql") + public String itemStorageHasStatement() { + return """ + SELECT COUNT(*) > 0 + FROM `bluemap_item_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `compression` = ? + """; + } + + + @Override + @Language("mysql") + public String gridStorageWriteStatement() { + return """ + REPLACE + INTO `bluemap_grid_storage_data` (`map`, `storage`, `x`, `z`, `compression`, `data`) VALUES (?, ?, ?, ?, ?, ?) """; } @Override @Language("mysql") - public String readMapTileStatement() { + public String gridStorageReadStatement() { return """ - SELECT t.`data` - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - INNER JOIN `bluemap_map_tile_compression` c - ON t.`compression` = c.`id` - WHERE m.`map_id` = ? - AND t.`lod` = ? - AND t.`x` = ? - AND t.`z` = ? - AND c.`compression` = ? - """; - } - - @Override - @Language("mysql") - public String deleteMapTileStatement() { - return """ - DELETE - FROM `bluemap_map_tile` + SELECT `data` + FROM `bluemap_grid_storage_data` WHERE `map` = ? - AND `lod` = ? + AND `storage` = ? AND `x` = ? AND `z` = ? AND `compression` = ? @@ -157,40 +217,60 @@ public String deleteMapTileStatement() { @Override @Language("mysql") - public String hasMapTileStatement() { - return """ - SELECT COUNT(*) > 0 - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - INNER JOIN `bluemap_map_tile_compression` c - ON t.`compression` = c.`id` - WHERE m.`map_id` = ? - AND t.`lod` = ? - AND t.`x` = ? - AND t.`z` = ? - AND c.`compression` = ? - """; - } - - @Override - @Language("mysql") - public String countAllMapTilesStatement() { - return """ - SELECT COUNT(*) - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - WHERE m.`map_id` = ? - """; - } - - @Override - @Language("mysql") - public String purgeMapTilesStatement() { + public String gridStorageDeleteStatement() { return """ DELETE - FROM `bluemap_map_tile` + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `x` = ? + AND `z` = ? + """; + } + + @Override + @Language("mysql") + public String gridStorageHasStatement() { + return """ + SELECT COUNT(*) > 0 + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `x` = ? + AND `z` = ? + AND `compression` = ? + """; + } + + @Override + @Language("mysql") + public String gridStorageListStatement() { + return """ + SELECT `x`, `z` + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `compression` = ? + LIMIT ? OFFSET ? + """; + } + + @Override + @Language("mysql") + public String gridStorageCountMapItemsStatement() { + return """ + SELECT COUNT(*) + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + """; + } + + @Override + @Language("mysql") + public String gridStoragePurgeMapStatement() { + return """ + DELETE + FROM `bluemap_grid_storage_data` WHERE `map` = ? LIMIT ? """; @@ -198,95 +278,11 @@ public String purgeMapTilesStatement() { @Override @Language("mysql") - public String listMapTilesStatement() { - return """ - SELECT t.`x`, t.`z` - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - INNER JOIN `bluemap_map_tile_compression` c - ON t.`compression` = c.`id` - WHERE m.`map_id` = ? - AND t.`lod` = ? - AND c.`compression` = ? - LIMIT ? OFFSET ? - """; - } - - @Override - @Language("mysql") - public String writeMapMetaStatement() { - return """ - REPLACE - INTO `bluemap_map_meta` (`map`, `key`, `value`) - VALUES (?, ?, ?) - """; - } - - @Override - @Language("mysql") - public String readMapMetaStatement() { - return """ - SELECT t.`value` - FROM `bluemap_map_meta` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - WHERE m.`map_id` = ? - AND t.`key` = ? - """; - } - - @Override - @Language("mysql") - public String deleteMapMetaStatement() { - return """ - DELETE - FROM `bluemap_map_meta` - WHERE `map` = ? - AND `key` = ? - """; - } - - @Override - @Language("mysql") - public String hasMapMetaStatement() { - return """ - SELECT COUNT(*) > 0 - FROM `bluemap_map_meta` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - WHERE m.`map_id` = ? - AND t.`key` = ? - """; - } - - @Override - @Language("mysql") - public String purgeMapTileTableStatement() { - return """ - DELETE - FROM `bluemap_map_tile` - WHERE `map` = ? - """; - } - - @Override - @Language("mysql") - public String purgeMapMetaTableStatement() { - return """ - DELETE - FROM `bluemap_map_meta` - WHERE `map` = ? - """; - } - - @Override - @Language("mysql") - public String deleteMapStatement() { + public String purgeMapStatement() { return """ DELETE FROM `bluemap_map` - WHERE `map` = ? + WHERE `id` = ? """; } @@ -335,8 +331,8 @@ public String createMapKeyStatement() { public String findCompressionKeyStatement() { return """ SELECT `id` - FROM `bluemap_map_tile_compression` - WHERE `compression` = ? + FROM `bluemap_compression` + WHERE `key` = ? """; } @@ -345,7 +341,47 @@ public String findCompressionKeyStatement() { public String createCompressionKeyStatement() { return """ INSERT - INTO `bluemap_map_tile_compression` (`compression`) + INTO `bluemap_compression` (`key`) + VALUES (?) + """; + } + + @Override + @Language("mysql") + public String findItemStorageKeyStatement() { + return """ + SELECT `id` + FROM `bluemap_item_storage` + WHERE `key` = ? + """; + } + + @Override + @Language("mysql") + public String createItemStorageKeyStatement() { + return """ + INSERT + INTO `bluemap_item_storage` (`key`) + VALUES (?) + """; + } + + @Override + @Language("mysql") + public String findGridStorageKeyStatement() { + return """ + SELECT `id` + FROM `bluemap_grid_storage` + WHERE `key` = ? + """; + } + + @Override + @Language("mysql") + public String createGridStorageKeyStatement() { + return """ + INSERT + INTO `bluemap_grid_storage` (`key`) VALUES (?) """; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/PostgreSQLCommandSet.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/PostgreSQLCommandSet.java index 69a57592..b632928a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/PostgreSQLCommandSet.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/PostgreSQLCommandSet.java @@ -48,69 +48,91 @@ map_id VARCHAR(190) UNIQUE NOT NULL @Language("postgresql") public String createCompressionTableStatement() { return """ - CREATE TABLE IF NOT EXISTS bluemap_map_tile_compression ( + CREATE TABLE IF NOT EXISTS bluemap_compression ( id SMALLSERIAL PRIMARY KEY, - compression VARCHAR(190) UNIQUE NOT NULL + key VARCHAR(190) UNIQUE NOT NULL ) """; } @Override @Language("postgresql") - public String createMapMetaTableStatement() { + public String createItemStorageTableStatement() { return """ - CREATE TABLE IF NOT EXISTS bluemap_map_meta ( - map SMALLINT NOT NULL - REFERENCES bluemap_map(id) - ON UPDATE RESTRICT - ON DELETE CASCADE, - key VARCHAR(190) NOT NULL, - value BYTEA NOT NULL, - PRIMARY KEY (map, key) + CREATE TABLE IF NOT EXISTS bluemap_item_storage ( + id SERIAL PRIMARY KEY, + key VARCHAR(190) UNIQUE NOT NULL ) """; } @Override @Language("postgresql") - public String createMapTileTableStatement() { + public String createItemStorageDataTableStatement() { return """ - CREATE TABLE IF NOT EXISTS bluemap_map_tile ( + CREATE TABLE IF NOT EXISTS bluemap_item_storage_data ( map SMALLINT NOT NULL REFERENCES bluemap_map (id) ON UPDATE RESTRICT ON DELETE CASCADE, - lod SMALLINT NOT NULL, - x INT NOT NULL, - z INT NOT NULL, + storage INT NOT NULL + REFERENCES bluemap_item_storage (id) + ON UPDATE RESTRICT + ON DELETE CASCADE, compression SMALLINT NOT NULL - REFERENCES bluemap_map_tile_compression (id) + REFERENCES bluemap_compression (id) ON UPDATE RESTRICT ON DELETE CASCADE, data BYTEA NOT NULL, - PRIMARY KEY (map, lod, x, z) + PRIMARY KEY (map, storage) ) """; } @Override @Language("postgresql") - public String fixLegacyCompressionIdsStatement() { + public String createGridStorageTableStatement() { return """ - UPDATE bluemap_map_tile_compression - SET compression = CONCAT('bluemap:', compression) - WHERE NOT compression LIKE '%:%' + CREATE TABLE IF NOT EXISTS bluemap_grid_storage ( + id SMALLSERIAL PRIMARY KEY, + key VARCHAR(190) UNIQUE NOT NULL + ) """; } @Override @Language("postgresql") - public String writeMapTileStatement() { + public String createGridStorageDataTableStatement() { + return """ + CREATE TABLE IF NOT EXISTS bluemap_grid_storage_data ( + map SMALLINT NOT NULL + REFERENCES bluemap_map (id) + ON UPDATE RESTRICT + ON DELETE CASCADE, + storage SMALLINT NOT NULL + REFERENCES bluemap_grid_storage (id) + ON UPDATE RESTRICT + ON DELETE CASCADE, + x INT NOT NULL, + z INT NOT NULL, + compression SMALLINT NOT NULL + REFERENCES bluemap_compression (id) + ON UPDATE RESTRICT + ON DELETE CASCADE, + data BYTEA NOT NULL, + PRIMARY KEY (map, storage, x, z) + ) + """; + } + + @Override + @Language("postgresql") + public String itemStorageWriteStatement() { return """ INSERT - INTO bluemap_map_tile (map, lod, x, z, compression, data) - VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT (map, lod, x, z) + INTO bluemap_item_storage_data (map, storage, compression, data) + VALUES (?, ?, ?, ?) + ON CONFLICT (map, storage) DO UPDATE SET compression = excluded.compression, data = excluded.data @@ -119,30 +141,61 @@ ON CONFLICT (map, lod, x, z) @Override @Language("postgresql") - public String readMapTileStatement() { + public String itemStorageReadStatement() { return """ - SELECT t.data - FROM bluemap_map_tile t - INNER JOIN bluemap_map m - ON t.map = m.id - INNER JOIN bluemap_map_tile_compression c - ON t.compression = c.id - WHERE m.map_id = ? - AND t.lod = ? - AND t.x = ? - AND t.z = ? - AND c.compression = ? + SELECT data + FROM bluemap_item_storage_data + WHERE map = ? + AND storage = ? + AND compression = ? """; } @Override @Language("postgresql") - public String deleteMapTileStatement() { + public String itemStorageDeleteStatement() { return """ DELETE - FROM bluemap_map_tile + FROM bluemap_item_storage_data WHERE map = ? - AND lod = ? + AND storage = ? + """; + } + + @Override + @Language("postgresql") + public String itemStorageHasStatement() { + return """ + SELECT COUNT(*) > 0 + FROM bluemap_item_storage_data + WHERE map = ? + AND storage = ? + AND compression = ? + """; + } + + @Override + @Language("postgresql") + public String gridStorageWriteStatement() { + return """ + INSERT + INTO bluemap_grid_storage_data (map, storage, x, z, compression, data) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT (map, storage, x, z) + DO UPDATE SET + compression = excluded.compression, + data = excluded.data + """; + } + + @Override + @Language("postgresql") + public String gridStorageReadStatement() { + return """ + SELECT data + FROM bluemap_grid_storage_data + WHERE map = ? + AND storage = ? AND x = ? AND z = ? AND compression = ? @@ -151,43 +204,63 @@ public String deleteMapTileStatement() { @Override @Language("postgresql") - public String hasMapTileStatement() { - return """ - SELECT COUNT(*) > 0 - FROM bluemap_map_tile t - INNER JOIN bluemap_map m - ON t.map = m.id - INNER JOIN bluemap_map_tile_compression c - ON t.compression = c.id - WHERE m.map_id = ? - AND t.lod = ? - AND t.x = ? - AND t.z = ? - AND c.compression = ? - """; - } - - @Override - @Language("postgresql") - public String countAllMapTilesStatement() { - return """ - SELECT COUNT(*) - FROM bluemap_map_tile t - INNER JOIN bluemap_map m - ON t.map = m.id - WHERE m.map_id = ? - """; - } - - @Override - @Language("postgresql") - public String purgeMapTilesStatement() { + public String gridStorageDeleteStatement() { return """ DELETE - FROM bluemap_map_tile + FROM bluemap_grid_storage_data + WHERE map = ? + AND storage = ? + AND x = ? + AND z = ? + """; + } + + @Override + @Language("postgresql") + public String gridStorageHasStatement() { + return """ + SELECT COUNT(*) > 0 + FROM bluemap_grid_storage_data + WHERE map = ? + AND storage = ? + AND x = ? + AND z = ? + AND compression = ? + """; + } + + @Override + @Language("postgresql") + public String gridStorageListStatement() { + return """ + SELECT x, z + FROM bluemap_grid_storage_data + WHERE map = ? + AND storage = ? + AND compression = ? + LIMIT ? OFFSET ? + """; + } + + @Override + @Language("postgresql") + public String gridStorageCountMapItemsStatement() { + return """ + SELECT COUNT(*) + FROM bluemap_grid_storage_data + WHERE map = ? + """; + } + + @Override + @Language("postgresql") + public String gridStoragePurgeMapStatement() { + return """ + DELETE + FROM bluemap_grid_storage_data WHERE CTID IN ( SELECT CTID - FROM bluemap_map_tile t + FROM bluemap_grid_storage_data t WHERE t.map = ? LIMIT ? ) @@ -196,98 +269,11 @@ WHERE CTID IN ( @Override @Language("postgresql") - public String listMapTilesStatement() { - return """ - SELECT t.x, t.z - FROM bluemap_map_tile t - INNER JOIN bluemap_map m - ON t.map = m.id - INNER JOIN bluemap_map_tile_compression c - ON t.compression = c.id - WHERE m.map_id = ? - AND t.lod = ? - AND c.compression = ? - LIMIT ? OFFSET ? - """; - } - - @Override - @Language("postgresql") - public String writeMapMetaStatement() { - return """ - INSERT - INTO bluemap_map_meta (map, key, value) - VALUES (?, ?, ?) - ON CONFLICT (map, key) - DO UPDATE SET - value = excluded.value - """; - } - - @Override - @Language("postgresql") - public String readMapMetaStatement() { - return """ - SELECT t.value - FROM bluemap_map_meta t - INNER JOIN bluemap_map m - ON t.map = m.id - WHERE m.map_id = ? - AND t.key = ? - """; - } - - @Override - @Language("postgresql") - public String deleteMapMetaStatement() { - return """ - DELETE - FROM bluemap_map_meta - WHERE map = ? - AND key = ? - """; - } - - @Override - @Language("postgresql") - public String hasMapMetaStatement() { - return """ - SELECT COUNT(*) > 0 - FROM bluemap_map_meta t - INNER JOIN bluemap_map m - ON t.map = m.id - WHERE m.map_id = ? - AND t.key = ? - """; - } - - @Override - @Language("postgresql") - public String purgeMapTileTableStatement() { - return """ - DELETE - FROM bluemap_map_tile - WHERE map = ? - """; - } - - @Override - @Language("postgresql") - public String purgeMapMetaTableStatement() { - return """ - DELETE - FROM bluemap_map_meta - WHERE map = ? - """; - } - - @Override - @Language("postgresql") - public String deleteMapStatement() { + public String purgeMapStatement() { return """ DELETE FROM bluemap_map - WHERE map = ? + WHERE id = ? """; } @@ -336,8 +322,8 @@ INTO bluemap_map (map_id) public String findCompressionKeyStatement() { return """ SELECT id - FROM bluemap_map_tile_compression - WHERE compression = ? + FROM bluemap_compression + WHERE key = ? """; } @@ -346,7 +332,47 @@ public String findCompressionKeyStatement() { public String createCompressionKeyStatement() { return """ INSERT - INTO bluemap_map_tile_compression (compression) + INTO bluemap_compression (key) + VALUES (?) + """; + } + + @Override + @Language("postgresql") + public String findItemStorageKeyStatement() { + return """ + SELECT id + FROM bluemap_item_storage + WHERE key = ? + """; + } + + @Override + @Language("postgresql") + public String createItemStorageKeyStatement() { + return """ + INSERT + INTO bluemap_item_storage (key) + VALUES (?) + """; + } + + @Override + @Language("postgresql") + public String findGridStorageKeyStatement() { + return """ + SELECT id + FROM bluemap_grid_storage + WHERE key = ? + """; + } + + @Override + @Language("postgresql") + public String createGridStorageKeyStatement() { + return """ + INSERT + INTO bluemap_grid_storage (key) VALUES (?) """; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/SqliteCommandSet.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/SqliteCommandSet.java index 2d35782a..6815cc99 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/SqliteCommandSet.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/sql/commandset/SqliteCommandSet.java @@ -48,51 +48,47 @@ public String createMapTableStatement() { @Language("sqlite") public String createCompressionTableStatement() { return """ - CREATE TABLE IF NOT EXISTS `bluemap_map_tile_compression` ( + CREATE TABLE IF NOT EXISTS `bluemap_compression` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, - `compression` TEXT UNIQUE NOT NULL + `key` TEXT UNIQUE NOT NULL ) STRICT """; } @Override @Language("sqlite") - public String createMapMetaTableStatement() { + public String createItemStorageTableStatement() { return """ - CREATE TABLE IF NOT EXISTS `bluemap_map_meta` ( - `map` INTEGER NOT NULL, - `key` TEXT NOT NULL, - `value` BLOB NOT NULL, - PRIMARY KEY (`map`, `key`), - CONSTRAINT `fk_bluemap_map_meta_map` - FOREIGN KEY (`map`) - REFERENCES `bluemap_map` (`id`) - ON UPDATE RESTRICT - ON DELETE CASCADE + CREATE TABLE IF NOT EXISTS `bluemap_item_storage` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `key` TEXT UNIQUE NOT NULL ) STRICT """; } @Override @Language("sqlite") - public String createMapTileTableStatement() { + public String createItemStorageDataTableStatement() { return """ - CREATE TABLE IF NOT EXISTS `bluemap_map_tile` ( + CREATE TABLE IF NOT EXISTS `bluemap_item_storage_data` ( `map` INTEGER NOT NULL, - `lod` INTEGER NOT NULL, - `x` INTEGER NOT NULL, - `z` INTEGER NOT NULL, + `storage` INTEGER NOT NULL, `compression` INTEGER NOT NULL, `data` BLOB NOT NULL, - PRIMARY KEY (`map`, `lod`, `x`, `z`), - CONSTRAINT `fk_bluemap_map_tile_map` + PRIMARY KEY (`map`, `storage`), + CONSTRAINT `fk_bluemap_item_map` FOREIGN KEY (`map`) REFERENCES `bluemap_map` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, - CONSTRAINT `fk_bluemap_map_tile_compression` + CONSTRAINT `fk_bluemap_item` + FOREIGN KEY (`storage`) + REFERENCES `bluemap_item_storage` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_bluemap_item_compression` FOREIGN KEY (`compression`) - REFERENCES `bluemap_map_tile_compression` (`id`) + REFERENCES `bluemap_compression` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) STRICT @@ -101,50 +97,110 @@ FOREIGN KEY (`compression`) @Override @Language("sqlite") - public String fixLegacyCompressionIdsStatement() { + public String createGridStorageTableStatement() { return """ - UPDATE `bluemap_map_tile_compression` - SET `compression` = 'bluemap:' || `compression` - WHERE NOT `compression` LIKE '%:%' + CREATE TABLE IF NOT EXISTS `bluemap_grid_storage` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `key` TEXT UNIQUE NOT NULL + ) STRICT """; } @Override @Language("sqlite") - public String writeMapTileStatement() { + public String createGridStorageDataTableStatement() { + return """ + CREATE TABLE IF NOT EXISTS `bluemap_grid_storage_data` ( + `map` INTEGER NOT NULL, + `storage` INTEGER NOT NULL, + `x` INTEGER NOT NULL, + `z` INTEGER NOT NULL, + `compression` INTEGER NOT NULL, + `data` BLOB NOT NULL, + PRIMARY KEY (`map`, `storage`, `x`, `z`), + CONSTRAINT `fk_bluemap_grid_map` + FOREIGN KEY (`map`) + REFERENCES `bluemap_map` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_bluemap_grid` + FOREIGN KEY (`storage`) + REFERENCES `bluemap_grid_storage` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_bluemap_grid_compression` + FOREIGN KEY (`compression`) + REFERENCES `bluemap_compression` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE + ) STRICT + """; + } + + @Override + @Language("sqlite") + public String itemStorageWriteStatement() { return """ REPLACE - INTO `bluemap_map_tile` (`map`, `lod`, `x`, `z`, `compression`, `data`) + INTO `bluemap_item_storage_data` (`map`, `storage`, `compression`, `data`) + VALUES (?, ?, ?, ?) + """; + } + + @Override + @Language("sqlite") + public String itemStorageReadStatement() { + return """ + SELECT `data` + FROM `bluemap_item_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `compression` = ? + """; + } + + @Override + @Language("sqlite") + public String itemStorageDeleteStatement() { + return """ + DELETE + FROM `bluemap_item_storage_data` + WHERE `map` = ? + AND `storage` = ? + """; + } + + @Override + @Language("sqlite") + public String itemStorageHasStatement() { + return """ + SELECT COUNT(*) > 0 + FROM `bluemap_item_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `compression` = ? + """; + } + + + @Override + @Language("sqlite") + public String gridStorageWriteStatement() { + return """ + REPLACE + INTO `bluemap_grid_storage_data` (`map`, `storage`, `x`, `z`, `compression`, `data`) VALUES (?, ?, ?, ?, ?, ?) """; } @Override @Language("sqlite") - public String readMapTileStatement() { + public String gridStorageReadStatement() { return """ - SELECT t.`data` - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - INNER JOIN `bluemap_map_tile_compression` c - ON t.`compression` = c.`id` - WHERE m.`map_id` = ? - AND t.`lod` = ? - AND t.`x` = ? - AND t.`z` = ? - AND c.`compression` = ? - """; - } - - @Override - @Language("sqlite") - public String deleteMapTileStatement() { - return """ - DELETE - FROM `bluemap_map_tile` + SELECT `data` + FROM `bluemap_grid_storage_data` WHERE `map` = ? - AND `lod` = ? + AND `storage` = ? AND `x` = ? AND `z` = ? AND `compression` = ? @@ -153,44 +209,64 @@ public String deleteMapTileStatement() { @Override @Language("sqlite") - public String hasMapTileStatement() { - return """ - SELECT COUNT(*) > 0 - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - INNER JOIN `bluemap_map_tile_compression` c - ON t.`compression` = c.`id` - WHERE m.`map_id` = ? - AND t.`lod` = ? - AND t.`x` = ? - AND t.`z` = ? - AND c.`compression` = ? - """; - } - - @Override - @Language("sqlite") - public String countAllMapTilesStatement() { - return """ - SELECT COUNT(*) - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - WHERE m.`map_id` = ? - """; - } - - @Override - @Language("sqlite") - public String purgeMapTilesStatement() { + public String gridStorageDeleteStatement() { return """ DELETE - FROM bluemap_map_tile + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `x` = ? + AND `z` = ? + """; + } + + @Override + @Language("sqlite") + public String gridStorageHasStatement() { + return """ + SELECT COUNT(*) > 0 + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `x` = ? + AND `z` = ? + AND `compression` = ? + """; + } + + @Override + @Language("sqlite") + public String gridStorageListStatement() { + return """ + SELECT `x`, `z` + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + AND `storage` = ? + AND `compression` = ? + LIMIT ? OFFSET ? + """; + } + + @Override + @Language("sqlite") + public String gridStorageCountMapItemsStatement() { + return """ + SELECT COUNT(*) + FROM `bluemap_grid_storage_data` + WHERE `map` = ? + """; + } + + @Override + @Language("sqlite") + public String gridStoragePurgeMapStatement() { + return """ + DELETE + FROM `bluemap_grid_storage_data` WHERE ROWID IN ( SELECT t.ROWID - FROM bluemap_map_tile t - WHERE t.map = ? + FROM `bluemap_grid_storage_data` t + WHERE t.`map` = ? LIMIT ? ) """; @@ -198,95 +274,11 @@ WHERE ROWID IN ( @Override @Language("sqlite") - public String listMapTilesStatement() { - return """ - SELECT t.`x`, t.`z` - FROM `bluemap_map_tile` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - INNER JOIN `bluemap_map_tile_compression` c - ON t.`compression` = c.`id` - WHERE m.`map_id` = ? - AND t.`lod` = ? - AND c.`compression` = ? - LIMIT ? OFFSET ? - """; - } - - @Override - @Language("sqlite") - public String writeMapMetaStatement() { - return """ - REPLACE - INTO `bluemap_map_meta` (`map`, `key`, `value`) - VALUES (?, ?, ?) - """; - } - - @Override - @Language("sqlite") - public String readMapMetaStatement() { - return """ - SELECT t.`value` - FROM `bluemap_map_meta` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - WHERE m.`map_id` = ? - AND t.`key` = ? - """; - } - - @Override - @Language("sqlite") - public String deleteMapMetaStatement() { - return """ - DELETE - FROM `bluemap_map_meta` - WHERE `map` = ? - AND `key` = ? - """; - } - - @Override - @Language("sqlite") - public String hasMapMetaStatement() { - return """ - SELECT COUNT(*) > 0 - FROM `bluemap_map_meta` t - INNER JOIN `bluemap_map` m - ON t.`map` = m.`id` - WHERE m.`map_id` = ? - AND t.`key` = ? - """; - } - - @Override - @Language("sqlite") - public String purgeMapTileTableStatement() { - return """ - DELETE - FROM `bluemap_map_tile` - WHERE `map` = ? - """; - } - - @Override - @Language("sqlite") - public String purgeMapMetaTableStatement() { - return """ - DELETE - FROM `bluemap_map_meta` - WHERE `map` = ? - """; - } - - @Override - @Language("sqlite") - public String deleteMapStatement() { + public String purgeMapStatement() { return """ DELETE FROM `bluemap_map` - WHERE `map` = ? + WHERE `id` = ? """; } @@ -335,8 +327,8 @@ public String createMapKeyStatement() { public String findCompressionKeyStatement() { return """ SELECT `id` - FROM `bluemap_map_tile_compression` - WHERE `compression` = ? + FROM `bluemap_compression` + WHERE `key` = ? """; } @@ -345,7 +337,47 @@ public String findCompressionKeyStatement() { public String createCompressionKeyStatement() { return """ INSERT - INTO `bluemap_map_tile_compression` (`compression`) + INTO `bluemap_compression` (`key`) + VALUES (?) + """; + } + + @Override + @Language("sqlite") + public String findItemStorageKeyStatement() { + return """ + SELECT `id` + FROM `bluemap_item_storage` + WHERE `key` = ? + """; + } + + @Override + @Language("sqlite") + public String createItemStorageKeyStatement() { + return """ + INSERT + INTO `bluemap_item_storage` (`key`) + VALUES (?) + """; + } + + @Override + @Language("sqlite") + public String findGridStorageKeyStatement() { + return """ + SELECT `id` + FROM `bluemap_grid_storage` + WHERE `key` = ? + """; + } + + @Override + @Language("sqlite") + public String createGridStorageKeyStatement() { + return """ + INSERT + INTO `bluemap_grid_storage` (`key`) VALUES (?) """; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/BiIntConsumer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/BiIntConsumer.java new file mode 100644 index 00000000..9ab4da27 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/BiIntConsumer.java @@ -0,0 +1,8 @@ +package de.bluecolored.bluemap.core.util; + +@FunctionalInterface +public interface BiIntConsumer { + + void accept(int a, int b); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java index 08d3b467..5fb27138 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java @@ -39,12 +39,13 @@ public class FileHelper { * once the stream gets closed. */ public static OutputStream createFilepartOutputStream(final Path file) throws IOException { - final Path partFile = getPartFile(file); - FileHelper.createDirectories(partFile.getParent()); + Path folder = file.toAbsolutePath().normalize().getParent(); + final Path partFile = folder.resolve(file.getFileName() + ".filepart"); + FileHelper.createDirectories(folder); OutputStream os = Files.newOutputStream(partFile, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); return new OnCloseOutputStream(os, () -> { if (!Files.exists(partFile)) return; - FileHelper.createDirectories(file.getParent()); + FileHelper.createDirectories(folder); FileHelper.move(partFile, file); }); } @@ -76,8 +77,4 @@ public static Path createDirectories(Path dir, FileAttribute... attrs) throws return Files.createDirectories(dir, attrs); } - private static Path getPartFile(Path file) { - return file.normalize().getParent().resolve(file.getFileName() + ".filepart"); - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Grid.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Grid.java index 83fcd799..7cb53e52 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Grid.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Grid.java @@ -30,10 +30,24 @@ import java.util.Collection; import java.util.Collections; import java.util.Objects; +import java.util.function.Consumer; public class Grid { - public static final Grid UNIT = new Grid(Vector2i.ONE); + public static final Grid UNIT = new Grid(Vector2i.ONE, Vector2i.ZERO) { + @Override public int getCellX(int posX) { return posX; } + @Override public int getCellY(int posY) { return posY; } + @Override public Vector2i getCell(Vector2i pos) { return pos; } + @Override public int getLocalX(int posX) { return 0; } + @Override public int getLocalY(int posY) { return 0; } + @Override public Vector2i getLocal(Vector2i pos) { return pos; } + @Override public int getCellMinX(int cellX) { return cellX; } + @Override public int getCellMinY(int cellY) { return cellY; } + @Override public Vector2i getCellMin(Vector2i cell) { return cell; } + @Override public int getCellMaxX(int cellX) { return cellX; } + @Override public int getCellMaxY(int cellY) { return cellY; } + @Override public Vector2i getCellMax(Vector2i cell) { return cell; } + }; private final Vector2i gridSize; private final Vector2i offset; @@ -158,13 +172,27 @@ public Vector2i getCellMax(Vector2i cell, Grid targetGrid) { ); } + public void forEachIntersecting(Vector2i cell, Grid targetGrid, Consumer action) { + forEachIntersecting(cell, targetGrid, (x, y) -> action.accept(new Vector2i(x, y))); + } + + public void forEachIntersecting(Vector2i cell, Grid targetGrid, BiIntConsumer action) { + Vector2i min = getCellMin(cell, targetGrid); + Vector2i max = getCellMax(cell, targetGrid); + for (int x = min.getX(); x <= max.getX(); x++){ + for (int y = min.getY(); y <= max.getY(); y++){ + action.accept(x, y); + } + } + } + public Collection getIntersecting(Vector2i cell, Grid targetGrid) { Vector2i min = getCellMin(cell, targetGrid); Vector2i max = getCellMax(cell, targetGrid); if (min.equals(max)) return Collections.singleton(min); - Collection intersects = new ArrayList<>(); + Collection intersects = new ArrayList<>((max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1)); for (int x = min.getX(); x <= max.getX(); x++){ for (int y = min.getY(); y <= max.getY(); y++){ intersects.add(new Vector2i(x, y)); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java index 778c933e..8d968ce3 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Lazy.java @@ -34,7 +34,7 @@ public class Lazy { private Supplier loader; @DebugDump - private T value; + private volatile T value; public Lazy(Supplier loader) { Objects.requireNonNull(loader); @@ -51,9 +51,13 @@ public Lazy(T value) { } public T getValue() { - if (!isLoaded()) { - this.value = loader.get(); - this.loader = null; + if (value == null) { + synchronized (this) { + if (value == null) { + this.value = loader.get(); + this.loader = null; + } + } } return this.value; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/PalettedArrayAdapter.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/PalettedArrayAdapter.java new file mode 100644 index 00000000..7498d4a7 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/PalettedArrayAdapter.java @@ -0,0 +1,75 @@ +package de.bluecolored.bluemap.core.util; + +import com.google.gson.reflect.TypeToken; +import de.bluecolored.bluenbt.BlueNBT; +import de.bluecolored.bluenbt.NBTReader; +import de.bluecolored.bluenbt.NBTWriter; +import de.bluecolored.bluenbt.TypeAdapter; +import de.bluecolored.bluenbt.adapter.ArrayAdapterFactory; +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.HashMap; + +@RequiredArgsConstructor +public class PalettedArrayAdapter implements TypeAdapter { + + private final Class type; + private final TypeAdapter paletteAdapter; + + @SuppressWarnings("unchecked") + public PalettedArrayAdapter(BlueNBT blueNBT, Class type) { + this.type = type; + this.paletteAdapter = ArrayAdapterFactory.INSTANCE.create((TypeToken) TypeToken.getArray(type), blueNBT).orElseThrow(); + } + + @SuppressWarnings("unchecked") + @Override + public T[] read(NBTReader reader) throws IOException { + reader.beginCompound(); + T[] palette = null; + byte[] data = null; + while (reader.hasNext()) { + String name = reader.name(); + switch (name) { + case "palette" -> palette = paletteAdapter.read(reader); + case "data" -> data = reader.nextArrayAsByteArray(); + default -> reader.skip(); + } + } + reader.endCompound(); + + if (palette == null || palette.length == 0) throw new IOException("Missing or empty palette"); + if (data == null) return (T[]) Array.newInstance(type, 0); + T[] result = (T[]) Array.newInstance(type, data.length); + for (int i = 0; i < data.length; i++) { + byte index = data[i]; + if (index >= palette.length) throw new IOException("Palette (size: " + palette.length + ") does not contain entry-index (" + index + ")"); + result[i] = palette[data[i]]; + } + + return result; + } + + @SuppressWarnings("unchecked") + @Override + public void write(T[] value, NBTWriter writer) throws IOException { + HashMap paletteMap = new HashMap<>(); + byte[] data = new byte[value.length]; + for (int i = 0; i < value.length; i++) { + byte index = paletteMap.computeIfAbsent(value[i], v -> (byte) paletteMap.size()); + data[i] = index; + } + + T[] palette = (T[]) Array.newInstance(type, paletteMap.size()); + paletteMap.forEach((k, v) -> palette[v] = k); + + writer.beginCompound(); + writer.name("palette"); + paletteAdapter.write(palette, writer); + writer.name("data").value(data); + writer.endCompound(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Registry.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Registry.java index a534c088..ff177f67 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Registry.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Registry.java @@ -24,26 +24,23 @@ */ package de.bluecolored.bluemap.core.util; +import lombok.NoArgsConstructor; import org.jetbrains.annotations.Nullable; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; +@NoArgsConstructor public class Registry { - private final ConcurrentHashMap entries; + private final ConcurrentHashMap entries = new ConcurrentHashMap<>(); - public Registry() { - this.entries = new ConcurrentHashMap<>(); - } + private final Set keys = Collections.unmodifiableSet(entries.keySet()); + private final Collection values = Collections.unmodifiableCollection(entries.values()); @SafeVarargs - public Registry(T... defaultEntires) { - this(); - for (T entry : defaultEntires) + public Registry(T... defaultEntries) { + for (T entry : defaultEntries) register(entry); } @@ -71,14 +68,14 @@ public boolean register(T entry) { * Returns an unmodifiable set of all keys this registry contains entries for */ public Set keys() { - return Collections.unmodifiableSet(entries.keySet()); + return keys; } /** * Returns an unmodifiable collection of entries in this registry */ public Collection values() { - return Collections.unmodifiableCollection(entries.values()); + return values; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/RegistryAdapter.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/RegistryAdapter.java new file mode 100644 index 00000000..23a8e059 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/RegistryAdapter.java @@ -0,0 +1,39 @@ +package de.bluecolored.bluemap.core.util; + +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluenbt.NBTReader; +import de.bluecolored.bluenbt.NBTWriter; +import de.bluecolored.bluenbt.TagType; +import de.bluecolored.bluenbt.TypeAdapter; +import lombok.RequiredArgsConstructor; + +import java.io.IOException; + +@RequiredArgsConstructor +public class RegistryAdapter implements TypeAdapter { + + private final Registry registry; + private final String defaultNamespace; + private final T fallback; + + @Override + public T read(NBTReader reader) throws IOException { + Key key = Key.parse(reader.nextString(), defaultNamespace); + T value = registry.get(key); + if (value != null) return value; + + Logger.global.noFloodWarning("unknown-registry-key-" + key.getFormatted(), "Failed to find registry-entry for key: " + key); + return fallback; + } + + @Override + public void write(T value, NBTWriter writer) throws IOException { + writer.value(value.getKey().getFormatted()); + } + + @Override + public TagType type() { + return TagType.STRING; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java index 23431efc..e66d7b57 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java @@ -26,69 +26,35 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.util.Key; +import de.bluecolored.bluemap.core.util.Keyed; import de.bluecolored.bluemap.core.util.math.Color; +import lombok.Getter; @DebugDump -public class Biome extends Key { +public interface Biome extends Keyed { - public static final Biome DEFAULT = new Biome("minecraft:ocean"); + Biome DEFAULT = new Default(); - private float humidity = 0.5f; - private float temp = 0.5f; - private final Color waterColor = new Color().set(4159204 | 0xFF000000).premultiplied(); + float getDownfall(); - private final Color overlayFoliageColor = new Color().premultiplied(); - private final Color overlayGrassColor = new Color().premultiplied(); + float getTemperature(); - public Biome(String formatted) { - super(formatted); - } + Color getWaterColor(); - public Biome(String formatted, float humidity, float temp, Color waterColor) { - this(formatted); - this.humidity = humidity; - this.temp = temp; - this.waterColor.set(waterColor).premultiplied(); - } + Color getOverlayFoliageColor(); - public Biome(String formatted, float humidity, float temp, Color waterColor, Color overlayFoliageColor, Color overlayGrassColor) { - this(formatted, humidity, temp, waterColor); - this.overlayFoliageColor.set(overlayFoliageColor).premultiplied(); - this.overlayGrassColor.set(overlayGrassColor).premultiplied(); - } + Color getOverlayGrassColor(); - public float getHumidity() { - return humidity; - } + @Getter + class Default implements Biome { - public float getTemp() { - return temp; - } + private final Key key = Key.bluemap("default"); + private final float downfall = 0.5f; + private final float temperature = 0.5f; + private final Color waterColor = new Color().set(4159204 | 0xFF000000).premultiplied(); + private final Color overlayFoliageColor = new Color().premultiplied(); + private final Color overlayGrassColor = new Color().premultiplied(); - public Color getWaterColor() { - return waterColor; - } - - public Color getOverlayFoliageColor() { - return overlayFoliageColor; - } - - public Color getOverlayGrassColor() { - return overlayGrassColor; - } - - @Override - public String toString() { - return "Biome{" + - "value='" + getValue() + '\'' + - ", namespace=" + getNamespace() + - ", formatted=" + getFormatted() + - ", humidity=" + humidity + - ", temp=" + temp + - ", waterColor=" + waterColor + - ", overlayFoliageColor=" + overlayFoliageColor + - ", overlayGrassColor=" + overlayGrassColor + - '}'; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java index 6a643cdd..7b5916ee 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java @@ -30,6 +30,7 @@ public interface Chunk { Chunk EMPTY_CHUNK = new Chunk() {}; + Chunk ERRORED_CHUNK = new Chunk() {}; default boolean isGenerated() { return false; @@ -51,8 +52,8 @@ default LightData getLightData(int x, int y, int z, LightData target) { return target.set(0, 0); } - default String getBiome(int x, int y, int z) { - return Biome.DEFAULT.getFormatted(); + default Biome getBiome(int x, int y, int z) { + return Biome.DEFAULT; } default int getMaxY(int x, int z) { @@ -75,5 +76,6 @@ default boolean hasOceanFloorHeights() { default int getOceanFloorY(int x, int z) { return 0; } - default @Nullable BlockEntity getBlockEntity(int x, int y, int z) { return null; }; + default @Nullable BlockEntity getBlockEntity(int x, int y, int z) { return null; } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java index f473fbc7..9f893a19 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java @@ -27,7 +27,7 @@ @FunctionalInterface public interface ChunkConsumer { - default boolean filter(int chunkX, int chunkZ, long lastModified) { + default boolean filter(int chunkX, int chunkZ, int lastModified) { return true; } @@ -36,10 +36,10 @@ default boolean filter(int chunkX, int chunkZ, long lastModified) { @FunctionalInterface interface ListOnly extends ChunkConsumer { - void accept(int chunkX, int chunkZ, long lastModified); + void accept(int chunkX, int chunkZ, int lastModified); @Override - default boolean filter(int chunkX, int chunkZ, long lastModified) { + default boolean filter(int chunkX, int chunkZ, int lastModified) { accept(chunkX, chunkZ, lastModified); return false; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java index 01da07c6..d5ac390f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Region.java @@ -37,7 +37,7 @@ class SingleChunkConsumer implements ChunkConsumer { private Chunk foundChunk = Chunk.EMPTY_CHUNK; @Override - public boolean filter(int x, int z, long lastModified) { + public boolean filter(int x, int z, int lastModified) { return x == chunkX && z == chunkZ; } @@ -53,7 +53,7 @@ public void accept(int chunkX, int chunkZ, Chunk chunk) { } /** - * Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, long)}.
+ * Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, int)}.
* And if (any only if) that method returned true, the chunk will be loaded and {@link ChunkConsumer#accept(int, int, Chunk)} * will be called with the loaded chunk. * @param consumer the consumer choosing which chunks to load and accepting them diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java index 66ba83c7..8015bd6f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -29,6 +29,7 @@ import de.bluecolored.bluemap.core.util.Grid; import java.util.Collection; +import java.util.function.Predicate; /** * Represents a World on the Server.
@@ -74,7 +75,11 @@ public interface World { /** * Loads all chunks from the specified region into the chunk cache (if there is a cache) */ - void preloadRegionChunks(int x, int z); + default void preloadRegionChunks(int x, int z) { + preloadRegionChunks(x, z, pos -> true); + } + + void preloadRegionChunks(int x, int z, Predicate chunkFilter); /** * Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/Block.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/Block.java index b9fc9257..f2eef512 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/Block.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/Block.java @@ -24,10 +24,7 @@ */ package de.bluecolored.bluemap.core.world.block; -import de.bluecolored.bluemap.core.world.BlockState; -import de.bluecolored.bluemap.core.world.Chunk; -import de.bluecolored.bluemap.core.world.LightData; -import de.bluecolored.bluemap.core.world.World; +import de.bluecolored.bluemap.core.world.*; import de.bluecolored.bluemap.core.world.block.entity.BlockEntity; import org.jetbrains.annotations.Nullable; @@ -36,11 +33,11 @@ public class Block> { private World world; private int x, y, z; - private Chunk chunk; + private @Nullable Chunk chunk; - private BlockState blockState; + private @Nullable BlockState blockState; private final LightData lightData = new LightData(-1, -1); - private String biomeId; + private @Nullable Biome biome; public Block(World world, int x, int y, int z) { set(world, x, y, z); @@ -82,7 +79,7 @@ public T set(int x, int y, int z) { protected void reset() { this.blockState = null; this.lightData.set(-1, -1); - this.biomeId = null; + this.biome = null; } public T add(int dx, int dy, int dz) { @@ -100,7 +97,7 @@ public T copy(Block source) { this.blockState = source.blockState; this.lightData.set(source.lightData.getSkyLight(), source.lightData.getBlockLight()); - this.biomeId = source.biomeId; + this.biome = source.biome; return self(); } @@ -136,9 +133,9 @@ public LightData getLightData() { return lightData; } - public String getBiomeId() { - if (biomeId == null) biomeId = getChunk().getBiome(x, y, z); - return biomeId; + public Biome getBiome() { + if (biome == null) biome = getChunk().getBiome(x, y, z); + return biome; } public int getSunLightLevel() { @@ -164,7 +161,7 @@ public String toString() { ", chunk=" + getChunk() + ", blockState=" + getBlockState() + ", lightData=" + getLightData() + - ", biomeId=" + getBiomeId() + + ", biome=" + getBiome() + '}'; } else { return "Block{" + diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/BlockNeighborhood.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/BlockNeighborhood.java index 24e08e42..82d112d7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/BlockNeighborhood.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/BlockNeighborhood.java @@ -25,7 +25,7 @@ package de.bluecolored.bluemap.core.world.block; import de.bluecolored.bluemap.core.map.hires.RenderSettings; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.world.World; public class BlockNeighborhood> extends ExtendedBlock { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/ExtendedBlock.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/ExtendedBlock.java index 50e1b181..ca7cb94f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/ExtendedBlock.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/block/ExtendedBlock.java @@ -25,8 +25,9 @@ package de.bluecolored.bluemap.core.world.block; import de.bluecolored.bluemap.core.map.hires.RenderSettings; -import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resources.pack.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.world.*; +import org.jetbrains.annotations.Nullable; import java.util.Objects; @@ -34,8 +35,7 @@ public class ExtendedBlock> extends Block { private final ResourcePack resourcePack; private final RenderSettings renderSettings; - private BlockProperties properties; - private Biome biome; + private @Nullable BlockProperties properties; private boolean insideRenderBoundsCalculated, insideRenderBounds; private boolean isCaveCalculated, isCave; @@ -51,7 +51,6 @@ protected void reset() { super.reset(); this.properties = null; - this.biome = null; this.insideRenderBoundsCalculated = false; this.isCaveCalculated = false; @@ -61,7 +60,6 @@ public T copy(ExtendedBlock source) { super.copy(source); this.properties = source.properties; - this.biome = source.biome; this.insideRenderBoundsCalculated = source.insideRenderBoundsCalculated; this.insideRenderBounds = source.insideRenderBounds; @@ -90,11 +88,6 @@ public BlockProperties getProperties() { return properties; } - public Biome getBiome() { - if (biome == null) biome = resourcePack.getBiome(getBiomeId()); - return biome; - } - public RenderSettings getRenderSettings() { return renderSettings; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java index 73b3efa3..57eb3c54 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java @@ -31,15 +31,12 @@ import de.bluecolored.bluemap.api.debug.DebugDump; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.resources.datapack.DataPack; +import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack; import de.bluecolored.bluemap.core.storage.compression.Compression; import de.bluecolored.bluemap.core.util.Grid; import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.util.Vector2iCache; -import de.bluecolored.bluemap.core.world.Chunk; -import de.bluecolored.bluemap.core.world.DimensionType; -import de.bluecolored.bluemap.core.world.Region; -import de.bluecolored.bluemap.core.world.World; +import de.bluecolored.bluemap.core.world.*; import de.bluecolored.bluemap.core.world.mca.chunk.ChunkLoader; import de.bluecolored.bluemap.core.world.mca.data.LevelData; import de.bluecolored.bluemap.core.world.mca.region.RegionType; @@ -53,7 +50,7 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; +import java.util.function.Predicate; @Getter @ToString @@ -68,8 +65,8 @@ public class MCAWorld implements World { private final String id; private final Path worldFolder; private final Key dimension; - private final LevelData levelData; private final DataPack dataPack; + private final LevelData levelData; private final DimensionType dimensionType; private final Vector3i spawnPoint; @@ -88,12 +85,12 @@ public class MCAWorld implements World { .expireAfterWrite(10, TimeUnit.MINUTES) .build(this::loadChunk); - private MCAWorld(Path worldFolder, Key dimension, LevelData levelData, DataPack dataPack) { + private MCAWorld(Path worldFolder, Key dimension, DataPack dataPack, LevelData levelData) { this.id = id(worldFolder, dimension); this.worldFolder = worldFolder; this.dimension = dimension; - this.levelData = levelData; this.dataPack = dataPack; + this.levelData = levelData; LevelData.Dimension dimensionData = levelData.getData().getWorldGenSettings().getDimensions().get(dimension.getFormatted()); if (dimensionData == null) { @@ -186,11 +183,20 @@ public Collection listRegions() { } @Override - public void preloadRegionChunks(int x, int z) { + public void preloadRegionChunks(int x, int z, Predicate chunkFilter) { try { - getRegion(x, z).iterateAllChunks((cx, cz, chunk) -> { - Vector2i chunkPos = VECTOR_2_I_CACHE.get(cx, cz); - chunkCache.put(chunkPos, chunk); + getRegion(x, z).iterateAllChunks(new ChunkConsumer() { + @Override + public boolean filter(int chunkX, int chunkZ, int lastModified) { + Vector2i chunkPos = VECTOR_2_I_CACHE.get(chunkX, chunkZ); + return chunkFilter.test(chunkPos); + } + + @Override + public void accept(int chunkX, int chunkZ, Chunk chunk) { + Vector2i chunkPos = VECTOR_2_I_CACHE.get(chunkX, chunkZ); + chunkCache.put(chunkPos, chunk); + } }); } catch (IOException ex) { Logger.global.logDebug("Unexpected exception trying to load preload region (x:" + x + ", z:" + z + "):" + ex); @@ -253,33 +259,17 @@ private Chunk loadChunk(int x, int z) { } Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException); - return Chunk.EMPTY_CHUNK; + return Chunk.ERRORED_CHUNK; } - public static MCAWorld load(Path worldFolder, Key dimension) throws IOException, InterruptedException { + public static MCAWorld load(Path worldFolder, Key dimension, DataPack dataPack) throws IOException, InterruptedException { // load level.dat Path levelFile = worldFolder.resolve("level.dat"); InputStream levelFileIn = Compression.GZIP.decompress(Files.newInputStream(levelFile)); LevelData levelData = MCAUtil.BLUENBT.read(levelFileIn, LevelData.class); - // load datapacks - DataPack dataPack = new DataPack(); - Path dataPackFolder = worldFolder.resolve("datapacks"); - if (Files.exists(dataPackFolder)) { - List roots; - try (var stream = Files.list(dataPackFolder)) { - roots = stream - .sorted(Comparator.reverseOrder()) - .collect(Collectors.toList()); - } - for (Path root : roots) { - dataPack.load(root); - } - } - dataPack.bake(); - // create world - return new MCAWorld(worldFolder, dimension, levelData, dataPack); + return new MCAWorld(worldFolder, dimension, dataPack, levelData); } public static String id(Path worldFolder, Key dimension) { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_13.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_13.java index 979c1264..1d2c96be 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_13.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_13.java @@ -149,11 +149,13 @@ public BlockState getBlockState(int x, int y, int z) { } @Override - public String getBiome(int x, int y, int z) { - if (this.biomes.length < 256) return Biome.DEFAULT.getFormatted(); + public Biome getBiome(int x, int y, int z) { + if (this.biomes.length < 256) return Biome.DEFAULT; int biomeIntIndex = (z & 0xF) << 4 | x & 0xF; - return LegacyBiomes.idFor(biomes[biomeIntIndex]); + + Biome biome = getWorld().getDataPack().getBiome(biomes[biomeIntIndex]); + return biome != null ? biome : Biome.DEFAULT; } @Override diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_15.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_15.java index 408438f9..0d1b4be7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_15.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_15.java @@ -34,8 +34,8 @@ public Chunk_1_15(MCAWorld world, Data data) { } @Override - public String getBiome(int x, int y, int z) { - if (this.biomes.length < 16) return Biome.DEFAULT.getFormatted(); + public Biome getBiome(int x, int y, int z) { + if (this.biomes.length < 16) return Biome.DEFAULT; int biomeIntIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2; @@ -43,7 +43,8 @@ public String getBiome(int x, int y, int z) { if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16; if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16; - return LegacyBiomes.idFor(biomes[biomeIntIndex]); + Biome biome = getWorld().getDataPack().getBiome(biomes[biomeIntIndex]); + return biome != null ? biome : Biome.DEFAULT; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_16.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_16.java index 648c338d..f3918574 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_16.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_16.java @@ -148,8 +148,8 @@ public BlockState getBlockState(int x, int y, int z) { } @Override - public String getBiome(int x, int y, int z) { - if (this.biomes.length < 16) return Biome.DEFAULT.getFormatted(); + public Biome getBiome(int x, int y, int z) { + if (this.biomes.length < 16) return Biome.DEFAULT; int biomeIntIndex = (y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2; @@ -157,7 +157,8 @@ public String getBiome(int x, int y, int z) { if (biomeIntIndex >= biomes.length) biomeIntIndex -= (((biomeIntIndex - biomes.length) >> 4) + 1) * 16; if (biomeIntIndex < 0) biomeIntIndex -= (biomeIntIndex >> 4) * 16; - return LegacyBiomes.idFor(biomes[biomeIntIndex]); + Biome biome = getWorld().getDataPack().getBiome(biomes[biomeIntIndex]); + return biome != null ? biome : Biome.DEFAULT; } @Override diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java index 9e65f541..b80b195c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/Chunk_1_18.java @@ -99,7 +99,7 @@ public Chunk_1_18(MCAWorld world, Data data) { // load sections into ordered array this.sections = new Section[1 + max - min]; for (SectionData sectionData : sectionsData) { - Section section = new Section(sectionData); + Section section = new Section(getWorld(), sectionData); int y = section.getSectionY(); if (min > y) min = y; @@ -145,9 +145,9 @@ public BlockState getBlockState(int x, int y, int z) { } @Override - public String getBiome(int x, int y, int z) { + public Biome getBiome(int x, int y, int z) { Section section = getSection(y >> 4); - if (section == null) return Biome.DEFAULT.getFormatted(); + if (section == null) return Biome.DEFAULT; return section.getBiome(x, y, z); } @@ -208,17 +208,23 @@ protected static class Section { private final int sectionY; private final BlockState[] blockPalette; - private final String[] biomePalette; + private final Biome[] biomePalette; private final PackedIntArrayAccess blocks; private final PackedIntArrayAccess biomes; private final byte[] blockLight; private final byte[] skyLight; - public Section(SectionData sectionData) { + public Section(MCAWorld world, SectionData sectionData) { this.sectionY = sectionData.y; this.blockPalette = sectionData.blockStates.palette; - this.biomePalette = sectionData.biomes.palette; + + this.biomePalette = new Biome[sectionData.biomes.palette.length]; + for (int i = 0; i < this.biomePalette.length; i++) { + Biome biome = world.getDataPack().getBiome(sectionData.biomes.palette[i]); + if (biome == null) biome = Biome.DEFAULT; + this.biomePalette[i] = biome; + } this.blocks = new PackedIntArrayAccess(sectionData.blockStates.data, BLOCKS_PER_SECTION); this.biomes = new PackedIntArrayAccess(Math.max(MCAUtil.ceilLog2(this.biomePalette.length), 1), sectionData.biomes.data); @@ -240,14 +246,14 @@ public BlockState getBlockState(int x, int y, int z) { return blockPalette[id]; } - public String getBiome(int x, int y, int z) { + public Biome getBiome(int x, int y, int z) { if (biomePalette.length == 1) return biomePalette[0]; - if (biomePalette.length == 0) return Biome.DEFAULT.getValue(); + if (biomePalette.length == 0) return Biome.DEFAULT; int id = biomes.get((y & 0b1100) << 2 | z & 0b1100 | (x & 0b1100) >> 2); if (id >= biomePalette.length) { Logger.global.noFloodWarning("biome-palette-warning", "Got biome-palette id " + id + " but palette has size of " + biomePalette.length + "."); - return Biome.DEFAULT.getValue(); + return Biome.DEFAULT; } return biomePalette[id]; @@ -309,7 +315,7 @@ public static class BlockStatesData { @Getter @SuppressWarnings("FieldMayBeFinal") public static class BiomesData { - private String[] palette = EMPTY_STRING_ARRAY; + private Key[] palette = EMPTY_KEY_ARRAY; private long[] data = EMPTY_LONG_ARRAY; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/LegacyBiomes.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/LegacyBiomes.java index a998ee1a..b5df5354 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/LegacyBiomes.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/LegacyBiomes.java @@ -24,93 +24,105 @@ */ package de.bluecolored.bluemap.core.world.mca.chunk; -import java.util.Arrays; +import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack; +import de.bluecolored.bluemap.core.util.Key; +import de.bluecolored.bluemap.core.world.Biome; +import org.jetbrains.annotations.Nullable; public class LegacyBiomes { - private static final String[] BIOME_IDS = new String[170]; + private static final @Nullable Key [] BIOME_KEYS = new Key[170]; static { - Arrays.fill(BIOME_IDS, "minecraft:ocean"); - BIOME_IDS[0] = "minecraft:ocean"; - BIOME_IDS[1] = "minecraft:plains"; - BIOME_IDS[2] = "minecraft:desert"; - BIOME_IDS[3] = "minecraft:mountains"; - BIOME_IDS[4] = "minecraft:forest"; - BIOME_IDS[5] = "minecraft:taiga"; - BIOME_IDS[6] = "minecraft:swamp"; - BIOME_IDS[7] = "minecraft:river"; - BIOME_IDS[8] = "minecraft:nether"; - BIOME_IDS[9] = "minecraft:the_end"; - BIOME_IDS[10] = "minecraft:frozen_ocean"; - BIOME_IDS[11] = "minecraft:frozen_river"; - BIOME_IDS[12] = "minecraft:snowy_tundra"; - BIOME_IDS[13] = "minecraft:snowy_mountains"; - BIOME_IDS[14] = "minecraft:mushroom_fields"; - BIOME_IDS[15] = "minecraft:mushroom_field_shore"; - BIOME_IDS[16] = "minecraft:beach"; - BIOME_IDS[17] = "minecraft:desert_hills"; - BIOME_IDS[18] = "minecraft:wooded_hills"; - BIOME_IDS[19] = "minecraft:taiga_hills"; - BIOME_IDS[20] = "minecraft:mountain_edge"; - BIOME_IDS[21] = "minecraft:jungle"; - BIOME_IDS[22] = "minecraft:jungle_hills"; - BIOME_IDS[23] = "minecraft:jungle_edge"; - BIOME_IDS[24] = "minecraft:deep_ocean"; - BIOME_IDS[25] = "minecraft:stone_shore"; - BIOME_IDS[26] = "minecraft:snowy_beach"; - BIOME_IDS[27] = "minecraft:birch_forest"; - BIOME_IDS[28] = "minecraft:birch_forest_hills"; - BIOME_IDS[29] = "minecraft:dark_forest"; - BIOME_IDS[30] = "minecraft:snowy_taiga"; - BIOME_IDS[31] = "minecraft:snowy_taiga_hills"; - BIOME_IDS[32] = "minecraft:giant_tree_taiga"; - BIOME_IDS[33] = "minecraft:giant_tree_taiga_hills"; - BIOME_IDS[34] = "minecraft:wooded_mountains"; - BIOME_IDS[35] = "minecraft:savanna"; - BIOME_IDS[36] = "minecraft:savanna_plateau"; - BIOME_IDS[37] = "minecraft:badlands"; - BIOME_IDS[38] = "minecraft:wooded_badlands_plateau"; - BIOME_IDS[39] = "minecraft:badlands_plateau"; - BIOME_IDS[40] = "minecraft:small_end_islands"; - BIOME_IDS[41] = "minecraft:end_midlands"; - BIOME_IDS[42] = "minecraft:end_highlands"; - BIOME_IDS[43] = "minecraft:end_barrens"; - BIOME_IDS[44] = "minecraft:warm_ocean"; - BIOME_IDS[45] = "minecraft:lukewarm_ocean"; - BIOME_IDS[46] = "minecraft:cold_ocean"; - BIOME_IDS[47] = "minecraft:deep_warm_ocean"; - BIOME_IDS[48] = "minecraft:deep_lukewarm_ocean"; - BIOME_IDS[49] = "minecraft:deep_cold_ocean"; - BIOME_IDS[50] = "minecraft:deep_frozen_ocean"; - BIOME_IDS[127] = "minecraft:the_void"; - BIOME_IDS[129] = "minecraft:sunflower_plains"; - BIOME_IDS[130] = "minecraft:desert_lakes"; - BIOME_IDS[131] = "minecraft:gravelly_mountains"; - BIOME_IDS[132] = "minecraft:flower_forest"; - BIOME_IDS[133] = "minecraft:taiga_mountains"; - BIOME_IDS[134] = "minecraft:swamp_hills"; - BIOME_IDS[140] = "minecraft:ice_spikes"; - BIOME_IDS[149] = "minecraft:modified_jungle"; - BIOME_IDS[151] = "minecraft:modified_jungle_edge"; - BIOME_IDS[155] = "minecraft:tall_birch_forest"; - BIOME_IDS[156] = "minecraft:tall_birch_hills"; - BIOME_IDS[157] = "minecraft:dark_forest_hills"; - BIOME_IDS[158] = "minecraft:snowy_taiga_mountains"; - BIOME_IDS[160] = "minecraft:giant_spruce_taiga"; - BIOME_IDS[161] = "minecraft:giant_spruce_taiga_hills"; - BIOME_IDS[162] = "minecraft:modified_gravelly_mountains"; - BIOME_IDS[163] = "minecraft:shattered_savanna"; - BIOME_IDS[164] = "minecraft:shattered_savanna_plateau"; - BIOME_IDS[165] = "minecraft:eroded_badlands"; - BIOME_IDS[166] = "minecraft:modified_wooded_badlands_plateau"; - BIOME_IDS[167] = "minecraft:modified_badlands_plateau"; - BIOME_IDS[168] = "minecraft:bamboo_jungle"; - BIOME_IDS[169] = "minecraft:bamboo_jungle_hills"; + BIOME_KEYS[ 0] = Key.minecraft("ocean"); + BIOME_KEYS[ 1] = Key.minecraft("plains"); + BIOME_KEYS[ 2] = Key.minecraft("desert"); + BIOME_KEYS[ 3] = Key.minecraft("mountains"); + BIOME_KEYS[ 4] = Key.minecraft("forest"); + BIOME_KEYS[ 5] = Key.minecraft("taiga"); + BIOME_KEYS[ 6] = Key.minecraft("swamp"); + BIOME_KEYS[ 7] = Key.minecraft("river"); + BIOME_KEYS[ 8] = Key.minecraft("nether"); + BIOME_KEYS[ 9] = Key.minecraft("the_end"); + BIOME_KEYS[ 10] = Key.minecraft("frozen_ocean"); + BIOME_KEYS[ 11] = Key.minecraft("frozen_river"); + BIOME_KEYS[ 12] = Key.minecraft("snowy_tundra"); + BIOME_KEYS[ 13] = Key.minecraft("snowy_mountains"); + BIOME_KEYS[ 14] = Key.minecraft("mushroom_fields"); + BIOME_KEYS[ 15] = Key.minecraft("mushroom_field_shore"); + BIOME_KEYS[ 16] = Key.minecraft("beach"); + BIOME_KEYS[ 17] = Key.minecraft("desert_hills"); + BIOME_KEYS[ 18] = Key.minecraft("wooded_hills"); + BIOME_KEYS[ 19] = Key.minecraft("taiga_hills"); + BIOME_KEYS[ 20] = Key.minecraft("mountain_edge"); + BIOME_KEYS[ 21] = Key.minecraft("jungle"); + BIOME_KEYS[ 22] = Key.minecraft("jungle_hills"); + BIOME_KEYS[ 23] = Key.minecraft("jungle_edge"); + BIOME_KEYS[ 24] = Key.minecraft("deep_ocean"); + BIOME_KEYS[ 25] = Key.minecraft("stone_shore"); + BIOME_KEYS[ 26] = Key.minecraft("snowy_beach"); + BIOME_KEYS[ 27] = Key.minecraft("birch_forest"); + BIOME_KEYS[ 28] = Key.minecraft("birch_forest_hills"); + BIOME_KEYS[ 29] = Key.minecraft("dark_forest"); + BIOME_KEYS[ 30] = Key.minecraft("snowy_taiga"); + BIOME_KEYS[ 31] = Key.minecraft("snowy_taiga_hills"); + BIOME_KEYS[ 32] = Key.minecraft("giant_tree_taiga"); + BIOME_KEYS[ 33] = Key.minecraft("giant_tree_taiga_hills"); + BIOME_KEYS[ 34] = Key.minecraft("wooded_mountains"); + BIOME_KEYS[ 35] = Key.minecraft("savanna"); + BIOME_KEYS[ 36] = Key.minecraft("savanna_plateau"); + BIOME_KEYS[ 37] = Key.minecraft("badlands"); + BIOME_KEYS[ 38] = Key.minecraft("wooded_badlands_plateau"); + BIOME_KEYS[ 39] = Key.minecraft("badlands_plateau"); + BIOME_KEYS[ 40] = Key.minecraft("small_end_islands"); + BIOME_KEYS[ 41] = Key.minecraft("end_midlands"); + BIOME_KEYS[ 42] = Key.minecraft("end_highlands"); + BIOME_KEYS[ 43] = Key.minecraft("end_barrens"); + BIOME_KEYS[ 44] = Key.minecraft("warm_ocean"); + BIOME_KEYS[ 45] = Key.minecraft("lukewarm_ocean"); + BIOME_KEYS[ 46] = Key.minecraft("cold_ocean"); + BIOME_KEYS[ 47] = Key.minecraft("deep_warm_ocean"); + BIOME_KEYS[ 48] = Key.minecraft("deep_lukewarm_ocean"); + BIOME_KEYS[ 49] = Key.minecraft("deep_cold_ocean"); + BIOME_KEYS[ 50] = Key.minecraft("deep_frozen_ocean"); + BIOME_KEYS[127] = Key.minecraft("the_void"); + BIOME_KEYS[129] = Key.minecraft("sunflower_plains"); + BIOME_KEYS[130] = Key.minecraft("desert_lakes"); + BIOME_KEYS[131] = Key.minecraft("gravelly_mountains"); + BIOME_KEYS[132] = Key.minecraft("flower_forest"); + BIOME_KEYS[133] = Key.minecraft("taiga_mountains"); + BIOME_KEYS[134] = Key.minecraft("swamp_hills"); + BIOME_KEYS[140] = Key.minecraft("ice_spikes"); + BIOME_KEYS[149] = Key.minecraft("modified_jungle"); + BIOME_KEYS[151] = Key.minecraft("modified_jungle_edge"); + BIOME_KEYS[155] = Key.minecraft("tall_birch_forest"); + BIOME_KEYS[156] = Key.minecraft("tall_birch_hills"); + BIOME_KEYS[157] = Key.minecraft("dark_forest_hills"); + BIOME_KEYS[158] = Key.minecraft("snowy_taiga_mountains"); + BIOME_KEYS[160] = Key.minecraft("giant_spruce_taiga"); + BIOME_KEYS[161] = Key.minecraft("giant_spruce_taiga_hills"); + BIOME_KEYS[162] = Key.minecraft("modified_gravelly_mountains"); + BIOME_KEYS[163] = Key.minecraft("shattered_savanna"); + BIOME_KEYS[164] = Key.minecraft("shattered_savanna_plateau"); + BIOME_KEYS[165] = Key.minecraft("eroded_badlands"); + BIOME_KEYS[166] = Key.minecraft("modified_wooded_badlands_plateau"); + BIOME_KEYS[167] = Key.minecraft("modified_badlands_plateau"); + BIOME_KEYS[168] = Key.minecraft("bamboo_jungle"); + BIOME_KEYS[169] = Key.minecraft("bamboo_jungle_hills"); } - public static String idFor(int legacyId) { - if (legacyId < 0 || legacyId >= BIOME_IDS.length) legacyId = 0; - return BIOME_IDS[legacyId]; + private final @Nullable Biome [] biomes = new Biome[BIOME_KEYS.length]; + + public LegacyBiomes(DataPack dataPack) { + for (int i = 0; i < biomes.length; i++) { + Key key = BIOME_KEYS[i]; + if (key != null) + biomes[i] = dataPack.getBiome(key); + } + } + + public @Nullable Biome forId(int legacyId) { + if (legacyId < 0 || legacyId >= biomes.length) return null; + return biomes[legacyId]; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java index 1cc0c766..e3be9503 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunk.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.world.mca.chunk; +import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.mca.MCAWorld; @@ -41,7 +42,7 @@ public abstract class MCAChunk implements Chunk { protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; protected static final int[] EMPTY_INT_ARRAY = new int[0]; protected static final long[] EMPTY_LONG_ARRAY = new long[0]; - protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + protected static final Key[] EMPTY_KEY_ARRAY = new Key[0]; protected static final BlockState[] EMPTY_BLOCKSTATE_ARRAY = new BlockState[0]; private final MCAWorld world; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java index c5021d94..2f74341b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/data/KeyDeserializer.java @@ -34,7 +34,7 @@ public class KeyDeserializer implements TypeDeserializer { @Override public Key read(NBTReader reader) throws IOException { - return new Key(reader.nextString()); + return Key.parse(reader.nextString(), Key.MINECRAFT_NAMESPACE); } } \ No newline at end of file diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java index 95764ef1..d1294dc2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java @@ -30,6 +30,7 @@ import de.bluecolored.bluemap.core.world.Region; import de.bluecolored.bluemap.core.world.mca.MCAWorld; import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk; +import lombok.Getter; import java.io.*; import java.nio.file.Files; @@ -58,6 +59,7 @@ * */ +@Getter public class LinearRegion implements Region { public static final String FILE_SUFFIX = ".linear"; @@ -164,7 +166,7 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException { if (length > 0) { int chunkX = chunkStartX + x; int chunkZ = chunkStartZ + z; - long timestamp = version == 2 ? chunkTimestamps[i] : newestTimestamp; + int timestamp = version == 2 ? chunkTimestamps[i] : (int) newestTimestamp; //TODO: check if in seconds or milliseconds if (consumer.filter(chunkX, chunkZ, timestamp)) { if (toBeSkipped > 0) skipNBytes(dIn, toBeSkipped); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java index 03e029fd..d1e420bc 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java @@ -32,7 +32,6 @@ import de.bluecolored.bluemap.core.world.mca.MCAWorld; import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk; import lombok.Getter; -import lombok.ToString; import java.io.IOException; import java.nio.ByteBuffer; @@ -43,10 +42,17 @@ import java.nio.file.StandardOpenOption; @Getter -@ToString public class MCARegion implements Region { public static final String FILE_SUFFIX = ".mca"; + public static final Compression[] CHUNK_COMPRESSION_MAP = new Compression[255]; + static { + CHUNK_COMPRESSION_MAP[0] = Compression.NONE; + CHUNK_COMPRESSION_MAP[1] = Compression.GZIP; + CHUNK_COMPRESSION_MAP[2] = Compression.DEFLATE; + CHUNK_COMPRESSION_MAP[3] = Compression.NONE; + CHUNK_COMPRESSION_MAP[4] = Compression.LZ4; + } private final MCAWorld world; private final Path regionFile; @@ -135,7 +141,7 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException { timestamp |= header[i] & 0xFF; // load chunk only if consumers filter returns true - if (consumer.filter(chunkX, chunkZ, timestamp * 1000L)) { + if (consumer.filter(chunkX, chunkZ, timestamp)) { i = xzChunk * 4; int offset = header[i++] << 16; offset |= (header[i++] & 0xFF) << 8; @@ -157,16 +163,10 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException { } private MCAChunk loadChunk(byte[] data, int size) throws IOException { - int compressionTypeId = data[4]; - Compression compression; - switch (compressionTypeId) { - case 0 : - case 3 : compression = Compression.NONE; break; - case 1 : compression = Compression.GZIP; break; - case 2 : compression = Compression.DEFLATE; break; - case 4 : compression = Compression.LZ4; break; - default: throw new IOException("Unknown chunk compression-id: " + compressionTypeId); - } + int compressionTypeId = Byte.toUnsignedInt(data[4]); + Compression compression = CHUNK_COMPRESSION_MAP[compressionTypeId]; + if (compression == null) + throw new IOException("Unknown chunk compression-id: " + compressionTypeId); return world.getChunkLoader().load(data, 5, size - 5, compression); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java index edf91739..85956eaf 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java @@ -24,71 +24,75 @@ */ package de.bluecolored.bluemap.core.world.mca.region; +import de.bluecolored.bluemap.core.util.Key; +import de.bluecolored.bluemap.core.util.Keyed; +import de.bluecolored.bluemap.core.util.Registry; import de.bluecolored.bluemap.core.world.Region; import de.bluecolored.bluemap.core.world.mca.MCAWorld; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.nio.file.Files; import java.nio.file.Path; -public enum RegionType { +public interface RegionType extends Keyed { - MCA (MCARegion::new, MCARegion.FILE_SUFFIX, MCARegion::getRegionFileName), - LINEAR (LinearRegion::new, LinearRegion.FILE_SUFFIX, LinearRegion::getRegionFileName); + RegionType MCA = new Impl(Key.bluemap("mca"), MCARegion.FILE_SUFFIX, MCARegion::new, MCARegion::getRegionFileName); + RegionType LINEAR = new Impl(Key.bluemap("linear"), LinearRegion.FILE_SUFFIX, LinearRegion::new, LinearRegion::getRegionFileName); - // we do this to improve performance, as calling values() creates a new array each time - private final static RegionType[] VALUES = values(); - private final static RegionType DEFAULT = MCA; + RegionType DEFAULT = MCA; + Registry REGISTRY = new Registry<>( + MCA, + LINEAR + ); - private final String fileSuffix; - private final RegionFactory regionFactory; - private final RegionFileNameFunction regionFileNameFunction; + String getFileSuffix(); - RegionType(RegionFactory regionFactory, String fileSuffix, RegionFileNameFunction regionFileNameFunction) { - this.fileSuffix = fileSuffix; - this.regionFactory = regionFactory; - this.regionFileNameFunction = regionFileNameFunction; - } + Region createRegion(MCAWorld world, Path regionFile); - public String getFileSuffix() { - return fileSuffix; - } + Path getRegionFile(Path regionFolder, int regionX, int regionZ); - public Region createRegion(MCAWorld world, Path regionFile) { - return this.regionFactory.create(world, regionFile); - } - - public String getRegionFileName(int regionX, int regionZ) { - return regionFileNameFunction.getRegionFileName(regionX, regionZ); - } - - public Path getRegionFile(Path regionFolder, int regionX, int regionZ) { - return regionFolder.resolve(getRegionFileName(regionX, regionZ)); - } - - @Nullable - public static RegionType forFileName(String fileName) { - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < VALUES.length; i++) { - RegionType regionType = VALUES[i]; - if (fileName.endsWith(regionType.fileSuffix)) + static @Nullable RegionType forFileName(String fileName) { + for (RegionType regionType : REGISTRY.values()) { + if (fileName.endsWith(regionType.getFileSuffix())) return regionType; } + return null; } - @NotNull - public static Region loadRegion(MCAWorld world, Path regionFolder, int regionX, int regionZ) { - //noinspection ForLoopReplaceableByForEach - for (int i = 0; i < VALUES.length; i++) { - RegionType regionType = VALUES[i]; + static @NotNull Region loadRegion(MCAWorld world, Path regionFolder, int regionX, int regionZ) { + for (RegionType regionType : REGISTRY.values()) { Path regionFile = regionType.getRegionFile(regionFolder, regionX, regionZ); if (Files.exists(regionFile)) return regionType.createRegion(world, regionFile); } return DEFAULT.createRegion(world, DEFAULT.getRegionFile(regionFolder, regionX, regionZ)); } + @RequiredArgsConstructor + class Impl implements RegionType { + + @Getter private final Key key; + @Getter private final String fileSuffix; + private final RegionFactory regionFactory; + private final RegionFileNameFunction regionFileNameFunction; + + public Region createRegion(MCAWorld world, Path regionFile) { + return this.regionFactory.create(world, regionFile); + } + + public String getRegionFileName(int regionX, int regionZ) { + return regionFileNameFunction.getRegionFileName(regionX, regionZ); + } + + public Path getRegionFile(Path regionFolder, int regionX, int regionZ) { + return regionFolder.resolve(getRegionFileName(regionX, regionZ)); + } + + } + @FunctionalInterface interface RegionFactory { Region create(MCAWorld world, Path regionFile); diff --git a/build.gradle.kts b/build.gradle.kts index c012da6f..ea5f6347 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ - tasks.register("clean") { gradle.includedBuilds.forEach { // workaround for https://github.com/neoforged/NeoGradle/issues/18 @@ -48,3 +47,11 @@ tasks.register("publish") { dependsOn(it.task(":publish")) } } + +// adding repositories here so intellij can download source-files and javadocs +repositories { + mavenCentral() + maven ("https://libraries.minecraft.net") + maven ("https://repo.papermc.io/repository/maven-public/") + maven ("https://repo.bluecolored.de/releases") +} diff --git a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index 17613005..c3e9714b 100644 --- a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -39,7 +39,6 @@ import de.bluecolored.bluemap.common.web.http.HttpRequestHandler; import de.bluecolored.bluemap.common.web.http.HttpServer; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.metrics.Metrics; @@ -65,18 +64,12 @@ public class BlueMapCLI { - private MinecraftVersion minecraftVersion = MinecraftVersion.LATEST_SUPPORTED; + private String minecraftVersion = null; private Path configFolder = Path.of("config"); public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRender, boolean forceGenerateWebapp, @Nullable String mapsToRender) throws ConfigurationException, IOException, InterruptedException { - //metrics report - if (blueMap.getConfig().getCoreConfig().isMetrics()) Metrics.sendReportAsync( - "cli", - blueMap.getConfig().getMinecraftVersion().getVersionString() - ); - if (blueMap.getConfig().getWebappConfig().isEnabled()) blueMap.createOrUpdateWebApp(forceGenerateWebapp); @@ -112,7 +105,7 @@ public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRende //update all maps int totalRegions = 0; for (BmMap map : maps.values()) { - MapUpdateTask updateTask = new MapUpdateTask(map, forceRender); + MapUpdateTask updateTask = new MapUpdateTask(map, s -> forceRender); renderManager.scheduleRenderTask(updateTask); totalRegions += updateTask.getRegions().size(); } @@ -156,13 +149,18 @@ public void run() { Logger.global.logInfo("Stopping..."); updateInfoTask.cancel(); saveTask.cancel(); - renderManager.stop(); - for (RegionFileWatchService watcher : regionFileWatchServices) { - watcher.close(); - } + regionFileWatchServices.forEach(RegionFileWatchService::close); regionFileWatchServices.clear(); + renderManager.removeAllRenderTasks(); + try { + renderManager.awaitIdle(true); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + + renderManager.stop(); try { renderManager.awaitShutdown(); } catch (InterruptedException e) { @@ -178,6 +176,12 @@ public void run() { Thread shutdownHook = new Thread(shutdown, "BlueMap-CLI-ShutdownHook"); Runtime.getRuntime().addShutdownHook(shutdownHook); + //metrics report + if (blueMap.getConfig().getCoreConfig().isMetrics()) Metrics.sendReportAsync( + "cli", + blueMap.getOrLoadMinecraftVersion().getId() + ); + // wait until done, then shutdown if not watching renderManager.awaitIdle(); Logger.global.logInfo("Your maps are now all up-to-date!"); @@ -291,14 +295,7 @@ public static void main(String[] args) { //minecraft version if (cmd.hasOption("v")) { - String versionString = cmd.getOptionValue("v"); - try { - cli.minecraftVersion = MinecraftVersion.of(versionString); - } catch (IllegalArgumentException e) { - Logger.global.logWarning("Could not determine a version from the provided version-string: '" + versionString + "'"); - System.exit(1); - return; - } + cli.minecraftVersion = cmd.getOptionValue("v"); } BlueMapConfigManager configs = BlueMapConfigManager.builder() diff --git a/implementations/fabric-1.18/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java b/implementations/fabric-1.18/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java index bea3506b..1213b877 100644 --- a/implementations/fabric-1.18/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java +++ b/implementations/fabric-1.18/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java @@ -29,17 +29,17 @@ import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.serverinterface.Player; -import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.Server; +import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.SharedConstants; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; @@ -128,8 +128,8 @@ public void onInitialize() { } @Override - public MinecraftVersion getMinecraftVersion() { - return new MinecraftVersion(1, 18); + public String getMinecraftVersion() { + return SharedConstants.getGameVersion().getId(); } @Override diff --git a/implementations/fabric-1.19.4/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java b/implementations/fabric-1.19.4/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java index c0e9eeb8..7a4a8d92 100644 --- a/implementations/fabric-1.19.4/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java +++ b/implementations/fabric-1.19.4/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java @@ -29,11 +29,10 @@ import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.serverinterface.Player; -import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.Server; +import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; @@ -129,12 +128,8 @@ public void onInitialize() { } @Override - public MinecraftVersion getMinecraftVersion() { - try { - return MinecraftVersion.of(SharedConstants.getGameVersion().getId()); - } catch (IllegalArgumentException ex) { - return MinecraftVersion.LATEST_SUPPORTED; - } + public String getMinecraftVersion() { + return SharedConstants.getGameVersion().getId(); } @Override diff --git a/implementations/fabric-1.20/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java b/implementations/fabric-1.20/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java index f964fe3c..2a40f4b9 100644 --- a/implementations/fabric-1.20/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java +++ b/implementations/fabric-1.20/src/main/java/de/bluecolored/bluemap/fabric/FabricMod.java @@ -29,11 +29,10 @@ import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.serverinterface.Player; -import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.Server; +import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; @@ -129,12 +128,8 @@ public void onInitialize() { } @Override - public MinecraftVersion getMinecraftVersion() { - try { - return MinecraftVersion.of(SharedConstants.getGameVersion().getId()); - } catch (IllegalArgumentException ex) { - return MinecraftVersion.LATEST_SUPPORTED; - } + public String getMinecraftVersion() { + return SharedConstants.getGameVersion().getId(); } @Override diff --git a/implementations/forge-1.18.1/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/implementations/forge-1.18.1/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index e899f182..41c0baf8 100644 --- a/implementations/forge-1.18.1/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/implementations/forge-1.18.1/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -29,12 +29,12 @@ import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.serverinterface.Player; -import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.Server; +import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; +import net.minecraft.SharedConstants; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -140,8 +140,8 @@ public void onTick(ServerTickEvent evt) { } @Override - public MinecraftVersion getMinecraftVersion() { - return new MinecraftVersion(1, 18, 1); + public String getMinecraftVersion() { + return SharedConstants.getCurrentVersion().getId(); } @Override diff --git a/implementations/forge-1.19.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/implementations/forge-1.19.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index 2a997211..c774f3db 100644 --- a/implementations/forge-1.19.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/implementations/forge-1.19.4/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -33,7 +33,6 @@ import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import net.minecraft.SharedConstants; import net.minecraft.core.registries.Registries; @@ -141,12 +140,8 @@ public void onTick(ServerTickEvent evt) { } @Override - public MinecraftVersion getMinecraftVersion() { - try { - return MinecraftVersion.of(SharedConstants.getCurrentVersion().getId()); - } catch (IllegalArgumentException ex) { - return MinecraftVersion.LATEST_SUPPORTED; - } + public String getMinecraftVersion() { + return SharedConstants.getCurrentVersion().getId(); } @Override diff --git a/implementations/forge-1.20/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/implementations/forge-1.20/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index 50ccc1a1..77ed826e 100644 --- a/implementations/forge-1.20/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/implementations/forge-1.20/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -29,11 +29,10 @@ import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.commands.Commands; import de.bluecolored.bluemap.common.serverinterface.Player; -import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.Server; +import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import net.minecraft.SharedConstants; import net.minecraft.core.registries.Registries; @@ -141,12 +140,8 @@ public void onTick(ServerTickEvent evt) { } @Override - public MinecraftVersion getMinecraftVersion() { - try { - return MinecraftVersion.of(SharedConstants.getCurrentVersion().getId()); - } catch (IllegalArgumentException ex) { - return MinecraftVersion.LATEST_SUPPORTED; - } + public String getMinecraftVersion() { + return SharedConstants.getCurrentVersion().getId(); } @Override diff --git a/implementations/neoforge-1.20.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java b/implementations/neoforge-1.20.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java index 94d4f607..6f8d4da3 100644 --- a/implementations/neoforge-1.20.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java +++ b/implementations/neoforge-1.20.2/src/main/java/de/bluecolored/bluemap/forge/ForgeMod.java @@ -33,7 +33,6 @@ import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import net.minecraft.SharedConstants; import net.minecraft.core.registries.Registries; @@ -140,12 +139,8 @@ public void onTick(TickEvent.ServerTickEvent evt) { } @Override - public MinecraftVersion getMinecraftVersion() { - try { - return MinecraftVersion.of(SharedConstants.getCurrentVersion().getId()); - } catch (IllegalArgumentException ex) { - return MinecraftVersion.LATEST_SUPPORTED; - } + public String getMinecraftVersion() { + return SharedConstants.getCurrentVersion().getId(); } @Override diff --git a/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlayer.java b/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlayer.java index 26de00f7..fd9c1aee 100644 --- a/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlayer.java +++ b/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlayer.java @@ -32,12 +32,11 @@ import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.metadata.MetadataValue; import org.bukkit.potion.PotionEffectType; -import java.util.EnumMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; public class BukkitPlayer implements Player { @@ -64,6 +63,12 @@ public class BukkitPlayer implements Player { public BukkitPlayer(UUID playerUUID) { this.uuid = playerUUID; update(); + + Material[] bestItems = Arrays.stream( Material.values() ) + .filter( Material::isItem ) + .sorted( Comparator.comparing( Material::isEdible, Boolean::compare ) ) + .toArray( Material[]::new ); + } @Override diff --git a/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java b/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java index 6589c383..661fdf90 100644 --- a/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java +++ b/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java @@ -28,11 +28,10 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.serverinterface.Player; -import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.Server; +import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.util.Key; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; @@ -53,8 +52,6 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class BukkitPlugin extends JavaPlugin implements Server, Listener { @@ -63,7 +60,6 @@ public class BukkitPlugin extends JavaPlugin implements Server, Listener { private final Plugin pluginInstance; private final EventForwarder eventForwarder; private final BukkitCommands commands; - private final MinecraftVersion minecraftVersion; private final Map onlinePlayerMap; private final List onlinePlayerList; @@ -76,18 +72,6 @@ public BukkitPlugin() { Logger.global.clear(); Logger.global.put(new JavaLogger(getLogger())); - //try to get best matching minecraft-version - MinecraftVersion version = MinecraftVersion.LATEST_SUPPORTED; - try { - String versionString = getServer().getBukkitVersion(); - Matcher versionMatcher = Pattern.compile("(\\d+(?:\\.\\d+){1,2})[-_].*").matcher(versionString); - if (!versionMatcher.matches()) throw new IllegalArgumentException(); - version = MinecraftVersion.of(versionMatcher.group(1)); - } catch (IllegalArgumentException e) { - Logger.global.logWarning("Failed to detect the minecraft version of this server! Using latest version: " + version.getVersionString()); - } - this.minecraftVersion = version; - this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); @@ -173,8 +157,8 @@ public void onDisable() { } @Override - public MinecraftVersion getMinecraftVersion() { - return minecraftVersion; + public String getMinecraftVersion() { + return Bukkit.getMinecraftVersion(); } @Override diff --git a/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java b/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java index f9030f96..5f8ca375 100644 --- a/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java +++ b/implementations/paper/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java @@ -25,7 +25,7 @@ package de.bluecolored.bluemap.bukkit; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; -import de.bluecolored.bluemap.core.resources.datapack.DataPack; +import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack; import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.world.mca.MCAWorld; import org.bukkit.Bukkit; diff --git a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java index 6ca7fd12..77ba2ff9 100644 --- a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java +++ b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitPlugin.java @@ -28,11 +28,10 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.serverinterface.Player; -import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.Server; +import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.util.Key; import org.bstats.bukkit.Metrics; @@ -62,7 +61,7 @@ public class BukkitPlugin extends JavaPlugin implements Server, Listener { private final Plugin pluginInstance; private final EventForwarder eventForwarder; private final BukkitCommands commands; - private final MinecraftVersion minecraftVersion; + private final String minecraftVersion; private int playerUpdateIndex = 0; private final Map onlinePlayerMap; @@ -75,14 +74,14 @@ public BukkitPlugin() { Logger.global.put(new JavaLogger(getLogger())); //try to get best matching minecraft-version - MinecraftVersion version = MinecraftVersion.LATEST_SUPPORTED; + String version = null; try { String versionString = getServer().getBukkitVersion(); Matcher versionMatcher = Pattern.compile("(\\d+(?:\\.\\d+){1,2})[-_].*").matcher(versionString); if (!versionMatcher.matches()) throw new IllegalArgumentException(); - version = MinecraftVersion.of(versionMatcher.group(1)); + version = versionMatcher.group(1); } catch (IllegalArgumentException e) { - Logger.global.logWarning("Failed to detect the minecraft version of this server! Using latest version: " + version.getVersionString()); + Logger.global.logWarning("Failed to detect the minecraft version of this server! Using latest version."); } this.minecraftVersion = version; @@ -169,8 +168,8 @@ public void onDisable() { } @Override - public MinecraftVersion getMinecraftVersion() { - return minecraftVersion; + public String getMinecraftVersion() { + return this.minecraftVersion; } @Override diff --git a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java index 906cf3f8..253090de 100644 --- a/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java +++ b/implementations/spigot/src/main/java/de/bluecolored/bluemap/bukkit/BukkitWorld.java @@ -25,7 +25,7 @@ package de.bluecolored.bluemap.bukkit; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; -import de.bluecolored.bluemap.core.resources.datapack.DataPack; +import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack; import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.world.mca.MCAWorld; import org.bukkit.Bukkit; diff --git a/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java b/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java index ef46c02b..0ed3f60c 100644 --- a/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java +++ b/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java @@ -36,11 +36,8 @@ import de.bluecolored.bluemap.common.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; import de.bluecolored.bluemap.core.BlueMap; -import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.sponge.SpongeCommands.SpongeCommandProxy; -import org.apache.maven.artifact.versioning.ArtifactVersion; -import org.spongepowered.api.Platform; import org.spongepowered.api.ResourceKey; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.Command; @@ -85,7 +82,6 @@ public class SpongePlugin implements Server { private final Map onlinePlayerMap; private final List onlinePlayerList; - private final MinecraftVersion minecraftVersion; private final LoadingCache worlds; @Inject @@ -98,13 +94,6 @@ public SpongePlugin(org.apache.logging.log4j.Logger logger, PluginContainer plug this.onlinePlayerMap = new ConcurrentHashMap<>(); this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>()); - final ArtifactVersion versionFromSponge = Sponge.platform().container(Platform.Component.GAME).metadata().version(); - this.minecraftVersion = new MinecraftVersion( - versionFromSponge.getMajorVersion(), - versionFromSponge.getMinorVersion(), - versionFromSponge.getIncrementalVersion() - ); - this.pluginInstance = new Plugin("sponge", this); this.commands = new SpongeCommands(pluginInstance); @@ -204,8 +193,8 @@ public void onPlayerLeave(ServerSideConnectionEvent.Disconnect evt) { } @Override - public MinecraftVersion getMinecraftVersion() { - return minecraftVersion; + public String getMinecraftVersion() { + return Sponge.platform().minecraftVersion().name(); } @Override diff --git a/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeWorld.java b/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeWorld.java index 16e496ab..5b5eb26d 100644 --- a/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeWorld.java +++ b/implementations/sponge/src/main/java/de/bluecolored/bluemap/sponge/SpongeWorld.java @@ -25,7 +25,7 @@ package de.bluecolored.bluemap.sponge; import de.bluecolored.bluemap.common.serverinterface.ServerWorld; -import de.bluecolored.bluemap.core.resources.datapack.DataPack; +import de.bluecolored.bluemap.core.resources.pack.datapack.DataPack; import de.bluecolored.bluemap.core.util.Key; import org.spongepowered.api.world.WorldTypes;