mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-22 02:26:00 +01:00
Renderstate rewrite, and moving biomes to datapacks, mc-version and vanilla-resources and resource-extensions rewrite (wip)
This commit is contained in:
parent
a6402850c9
commit
81fe41fd2b
@ -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<String, StorageConfig> getStorageConfigs();
|
||||
|
||||
@Nullable Path getResourcePacksFolder();
|
||||
@Nullable Path getPacksFolder();
|
||||
|
||||
@Nullable Path getModsFolder();
|
||||
|
||||
|
@ -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<String, World> worlds;
|
||||
private final Map<String, BmMap> 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<Path> 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<Path> resourcePackRoots = new ArrayList<>();
|
||||
|
||||
if (resourcePackFolder != null) {
|
||||
// load from resourcepack folder
|
||||
try (Stream<Path> 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<Path> 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<Path> 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<Path> getWorldFolders() {
|
||||
Set<Path> 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<Path> worldPacks = List.of();
|
||||
Path worldPacksFolder = worldFolder.resolve("datapacks");
|
||||
if (Files.isDirectory(worldPacksFolder)) {
|
||||
try (Stream<Path> worldPacksStream = Files.list(worldPacksFolder)) {
|
||||
worldPacks = worldPacksStream.toList();
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationException("Failed to access the worlds datapacks folder.", e);
|
||||
}
|
||||
}
|
||||
return folders;
|
||||
|
||||
Deque<Path> 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<Path> getPackRoots(Path... additionalRoots) throws ConfigurationException, InterruptedException {
|
||||
return getPackRoots(List.of(additionalRoots));
|
||||
}
|
||||
|
||||
private synchronized Deque<Path> getPackRoots(Iterable<Path> 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<Path> packRoots = new LinkedList<>();
|
||||
|
||||
// load from pack folder
|
||||
if (packsFolder != null && Files.isDirectory(packsFolder)) {
|
||||
try (Stream<Path> 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<Path> 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() {
|
||||
|
@ -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<Vector2i> 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
|
||||
|
@ -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<String, MapConfig> mapConfigs;
|
||||
private final Map<String, StorageConfig> 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<ServerWorld> 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;
|
||||
}
|
||||
|
||||
|
@ -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<Dialect> 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<Database, CommandSet> commandSetProvider;
|
||||
|
||||
@Override
|
||||
public boolean supports(String connectionUrl) {
|
||||
return connectionUrl.startsWith(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandSet createCommandSet(Database database) {
|
||||
return commandSetProvider.apply(database);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<String, String> 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");
|
||||
|
@ -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) {
|
||||
|
@ -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 + "'.");
|
||||
}
|
||||
|
@ -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<S> 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<S> context) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int debugMapCommand(CommandContext<S> context) {
|
||||
final CommandSource source = commandSourceInterface.apply(context.getSource());
|
||||
|
||||
// parse arguments
|
||||
String mapId = context.getArgument("map", String.class);
|
||||
Optional<Integer> x = getOptionalArgument(context, "x", Integer.class);
|
||||
Optional<Integer> 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<String, Object> 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<S> 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<String, Object> 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<S> 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<S> 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!"));
|
||||
|
@ -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<String> getDetail() {
|
||||
if (this.currentTaskIndex >= this.tasks.size()) return Optional.empty();
|
||||
return Optional.ofNullable(this.tasks.get(this.currentTaskIndex).getDescription());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<RenderTask> {
|
||||
@ -47,7 +49,7 @@ public MapUpdateTask(BmMap map) {
|
||||
this(map, getRegions(map));
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, boolean force) {
|
||||
public MapUpdateTask(BmMap map, Predicate<TileState> 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<TileState> force) {
|
||||
this(map, getRegions(map, center, radius), force);
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, Collection<Vector2i> regions) {
|
||||
this(map, regions, false);
|
||||
this(map, regions, s -> false);
|
||||
}
|
||||
|
||||
public MapUpdateTask(BmMap map, Collection<Vector2i> regions, boolean force) {
|
||||
public MapUpdateTask(BmMap map, Collection<Vector2i> regions, Predicate<TileState> 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<Vector2i> getRegions() {
|
||||
return regions;
|
||||
}
|
||||
|
||||
private static Collection<RenderTask> createTasks(BmMap map, Collection<Vector2i> regions, boolean force) {
|
||||
private static Collection<RenderTask> createTasks(BmMap map, Collection<Vector2i> regions, Predicate<TileState> force) {
|
||||
ArrayList<WorldRegionRenderTask> regionTasks = new ArrayList<>(regions.size());
|
||||
regions.forEach(region -> regionTasks.add(new WorldRegionRenderTask(map, region, force)));
|
||||
|
||||
@ -99,33 +101,47 @@ private static Collection<RenderTask> createTasks(BmMap map, Collection<Vector2i
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private static List<Vector2i> getRegions(BmMap map) {
|
||||
private static Collection<Vector2i> getRegions(BmMap map) {
|
||||
return getRegions(map, null, -1);
|
||||
}
|
||||
|
||||
private static List<Vector2i> getRegions(BmMap map, Vector2i center, int radius) {
|
||||
private static Collection<Vector2i> getRegions(BmMap map, Vector2i center, int radius) {
|
||||
World world = map.getWorld();
|
||||
Grid regionGrid = world.getRegionGrid();
|
||||
Predicate<Vector2i> regionFilter = map.getMapSettings().getRenderBoundariesCellFilter(regionGrid);
|
||||
|
||||
Predicate<Vector2i> regionBoundsFilter = map.getMapSettings().getCellRenderBoundariesFilter(regionGrid, true);
|
||||
Predicate<Vector2i> 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<Vector2i> regions = new ArrayList<>();
|
||||
Vector2i halfCell = regionGrid.getGridSize().div(2);
|
||||
long increasedRadiusSquared = (long) Math.pow(radius + Math.ceil(halfCell.length()), 2);
|
||||
Set<Vector2i> 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<GridStorage.Cell> 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;
|
||||
|
@ -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) + "%)");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<TileState> force;
|
||||
|
||||
private Deque<Vector2i> 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<TileState> 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<Vector2l> 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<Vector2i> 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<Vector2i> 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<WorldRegionRenderTask> 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);
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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<Route> routes;
|
||||
public final LinkedList<Route> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -1,181 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<MinecraftVersion> {
|
||||
|
||||
private static final Pattern VERSION_REGEX = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)(?:\\.(?<patch>\\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<MinecraftResource> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<String, MarkerSet> markerSets;
|
||||
|
||||
private Predicate<Vector2i> tileFilter;
|
||||
@Setter private Predicate<Vector2i> 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<String, MarkerSet> getMarkerSets() {
|
||||
return markerSets;
|
||||
}
|
||||
|
||||
public Predicate<Vector2i> getTileFilter() {
|
||||
return tileFilter;
|
||||
}
|
||||
|
||||
public void setTileFilter(Predicate<Vector2i> tileFilter) {
|
||||
this.tileFilter = tileFilter;
|
||||
}
|
||||
|
||||
public long getAverageNanosPerTile() {
|
||||
return renderTimeSumNanos / tilesRendered;
|
||||
}
|
||||
|
@ -1,119 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<Vector2i, Long> 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<Vector2i, Long> 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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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<Vector2i> 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<Vector2i> getCellRenderBoundariesFilter(Grid grid, boolean allowPartiallyIncludedCells) {
|
||||
return cell -> isInsideRenderBoundaries(cell, grid, allowPartiallyIncludedCells);
|
||||
}
|
||||
|
||||
boolean isSaveHiresLayer();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<T extends CellStorage.Cell> {
|
||||
|
||||
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<T> type;
|
||||
private final LinkedHashMap<Vector2i, T> cells = new LinkedHashMap<>(
|
||||
8,
|
||||
0.75f,
|
||||
true
|
||||
) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<Vector2i, T> eldest) {
|
||||
if (this.size() <= CACHE_SIZE) return false;
|
||||
saveCell(eldest.getKey(), eldest.getValue());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
public CellStorage(GridStorage storage, Class<T> 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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<ChunkInfoRegion> {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<TileInfoRegion> {
|
||||
|
||||
static final int SHIFT = 5;
|
||||
public static final Grid GRID = new Grid(1 << SHIFT);
|
||||
|
||||
@Getter private int lastRenderTime = -1;
|
||||
private final Map<TileState, Integer> chunkStateCounts = new ConcurrentHashMap<>();
|
||||
|
||||
public MapTileState(GridStorage storage) {
|
||||
super(storage, TileInfoRegion.class);
|
||||
}
|
||||
|
||||
public synchronized void load() {
|
||||
lastRenderTime = -1;
|
||||
chunkStateCounts.clear();
|
||||
|
||||
try (Stream<GridStorage.Cell> 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<TileState, Integer> getChunkStateCounts() {
|
||||
return Collections.unmodifiableMap(chunkStateCounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized TileInfoRegion createNewCell() {
|
||||
TileInfoRegion region = TileInfoRegion.create();
|
||||
region.populateSummaryMap(chunkStateCounts);
|
||||
return region;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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<TileState, Integer> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<TileState> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<VersionInfo> {
|
||||
|
||||
public VersionInfoAdapter(Class<VersionInfo> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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<VersionRange> {
|
||||
|
||||
public Adapter(Class<VersionRange> 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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<String, Version> 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<Version> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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<LocalDateTime> {
|
||||
|
||||
@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()));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<String, Biome> 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);
|
||||
}
|
||||
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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();
|
||||
|
||||
}
|
@ -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<Path> 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 <T> void loadResource(Path root, Path file, Loader<T> loader, Map<? super ResourcePath<T>, T> resultMap) {
|
||||
try {
|
||||
ResourcePath<T> 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<Path> 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<Path> 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> {
|
||||
T load(ResourcePath<T> resourcePath) throws IOException;
|
||||
}
|
||||
|
||||
protected interface PathLoader {
|
||||
void load(Path root) throws IOException;
|
||||
}
|
||||
|
||||
}
|
@ -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<Key, DimensionType> dimensionTypes = new HashMap<>();
|
||||
private final Map<Key, Biome> 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<Path> 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 <T> void loadResource(Path root, Path file, Loader<T> loader, Map<Key, T> resultMap) {
|
||||
try {
|
||||
ResourcePath<T> 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<Path> 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<Path> 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> {
|
||||
T load() throws IOException;
|
||||
public @Nullable Biome getBiome(int legacyId) {
|
||||
return legacyBiomes.forId(legacyId);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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<BlockState> MISSING_BLOCK_STATE = new ResourcePath<>("bluemap", "missing");
|
||||
public static final ResourcePath<BlockModel> MISSING_BLOCK_MODEL = new ResourcePath<>("bluemap", "block/missing");
|
||||
public static final ResourcePath<Texture> MISSING_TEXTURE = new ResourcePath<>("bluemap", "block/missing");
|
||||
|
||||
private final Map<String, ResourcePath<BlockState>> blockStatePaths;
|
||||
private final Map<ResourcePath<BlockState>, BlockState> blockStates;
|
||||
private final Map<String, ResourcePath<BlockModel>> blockModelPaths;
|
||||
private final Map<ResourcePath<BlockModel>, BlockModel> blockModels;
|
||||
private final Map<String, ResourcePath<Texture>> texturePaths;
|
||||
private final Map<ResourcePath<Texture>, Texture> textures;
|
||||
private final Map<ResourcePath<BufferedImage>, BufferedImage> colormaps;
|
||||
|
||||
private final Map<ResourcePath<BufferedImage>, BufferedImage> colormaps;
|
||||
private final BlockColorCalculatorFactory colorCalculatorFactory;
|
||||
private final BiomeConfig biomeConfig;
|
||||
private final BlockPropertiesConfig blockPropertiesConfig;
|
||||
|
||||
private final Map<String, ResourcePath<BlockState>> blockStatePaths;
|
||||
private final Map<String, ResourcePath<Texture>> texturePaths;
|
||||
private final LoadingCache<de.bluecolored.bluemap.core.world.BlockState, BlockProperties> 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<BlockState> getBlockStatePath(String formatted) {
|
||||
return blockStatePaths.get(formatted);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockState getBlockState(de.bluecolored.bluemap.core.world.BlockState blockState) {
|
||||
ResourcePath<BlockState> path = blockStatePaths.get(blockState.getFormatted());
|
||||
return path != null ? path.getResource(this::getBlockState) : MISSING_BLOCK_STATE.getResource(this::getBlockState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockState getBlockState(ResourcePath<BlockState> path) {
|
||||
BlockState blockState = blockStates.get(path);
|
||||
return blockState != null ? blockState : MISSING_BLOCK_STATE.getResource(blockStates::get);
|
||||
}
|
||||
|
||||
public Map<ResourcePath<BlockState>, BlockState> getBlockStates() {
|
||||
return blockStates;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ResourcePath<BlockModel> getBlockModelPath(String formatted) {
|
||||
return blockModelPaths.get(formatted);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockModel getBlockModel(ResourcePath<BlockModel> path) {
|
||||
BlockModel blockModel = blockModels.get(path);
|
||||
return blockModel != null ? blockModel : MISSING_BLOCK_MODEL.getResource(blockModels::get);
|
||||
}
|
||||
|
||||
public Map<ResourcePath<BlockModel>, BlockModel> getBlockModels() {
|
||||
return blockModels;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ResourcePath<Texture> getTexturePath(String formatted) {
|
||||
return texturePaths.get(formatted);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Texture getTexture(ResourcePath<Texture> path) {
|
||||
Texture texture = textures.get(path);
|
||||
return texture != null ? texture : MISSING_TEXTURE.getResource(textures::get);
|
||||
}
|
||||
|
||||
public Map<ResourcePath<Texture>, 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<Path> roots) throws IOException, InterruptedException {
|
||||
Logger.global.logInfo("Loading resources...");
|
||||
|
||||
@ -209,43 +118,9 @@ public synchronized void loadResources(Iterable<Path> 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<Texture> 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 <T> void loadResource(Path root, Path file, Loader<T> loader, Map<ResourcePath<T>, T> resultMap) {
|
||||
try {
|
||||
ResourcePath<T> 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<BlockState> 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<BlockState> 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<BlockModel> path) {
|
||||
BlockModel blockModel = blockModels.get(path);
|
||||
return blockModel != null ? blockModel : MISSING_BLOCK_MODEL.getResource(blockModels::get);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ResourcePath<Texture> getTexturePath(String formatted) {
|
||||
return texturePaths.get(formatted);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Texture getTexture(ResourcePath<Texture> path) {
|
||||
Texture texture = textures.get(path);
|
||||
return texture != null ? texture : MISSING_TEXTURE.getResource(textures::get);
|
||||
}
|
||||
|
||||
public Map<ResourcePath<Texture>, 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<Path> 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<Path> 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> {
|
||||
T load() throws IOException;
|
||||
}
|
||||
|
||||
private interface PathLoader {
|
||||
void load(Path root) throws IOException;
|
||||
return props.build();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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 <b>existing</b> 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
|
||||
|
@ -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
|
@ -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}.<br>
|
||||
* 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}.<br>
|
||||
* 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);
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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.<br>
|
||||
* <br>
|
||||
* If this method is invoked multiple times with the same <code>mapId</code>, it is important that the returned MapStorage should at least
|
||||
* be equal (<code>equals() == true</code>) to the previously returned storages!
|
||||
*/
|
||||
MapStorage map(String mapId);
|
||||
|
||||
|
@ -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 <b>not</b> compress the provided InputStream.
|
||||
*/
|
||||
public CompressedInputStream(InputStream in) {
|
||||
this(in, Compression.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CompressedInputStream from an <b>already compressed</b> {@link InputStream} and the {@link Compression}
|
||||
* it is compressed with.
|
||||
|
@ -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<Cell> stream() throws IOException {
|
||||
return storage.files(root)
|
||||
if (!Files.exists(root)) return Stream.empty();
|
||||
return Files.walk(root)
|
||||
.filter(Files::isRegularFile)
|
||||
.<Cell>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<String> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<Integer, GridStorage> 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<Path> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,11 +39,11 @@ public class FileStorage implements Storage {
|
||||
private final Path root;
|
||||
private final LoadingCache<String, FileMapStorage> 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
|
||||
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<Integer, GridStorage> 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)<br>
|
||||
* Basically, this method should mimic the functionality of
|
||||
* {@link java.nio.file.Files#walk(Path, FileVisitOption...)}
|
||||
*/
|
||||
public abstract Stream<Path> files(Path path) throws IOException;
|
||||
|
||||
}
|
@ -78,6 +78,7 @@ public <R> R run(ConnectionFunction<R> 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 {
|
||||
|
@ -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<Cell> 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
|
@ -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
|
@ -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<Integer, GridStorage> lowresGridStorages;
|
||||
|
||||
private final SingleItemStorage renderStateStorage;
|
||||
private final SingleItemStorage settingsStorage;
|
||||
private final SingleItemStorage texturesStorage;
|
||||
private final SingleItemStorage markersStorage;
|
||||
private final SingleItemStorage playersStorage;
|
||||
private final Cache<Key, ItemStorage> itemStorages = Caffeine.newBuilder().build();
|
||||
private final Cache<Key, GridStorage> 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();
|
||||
|
@ -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<String, Integer> mapKeys = Caffeine.newBuilder()
|
||||
protected final LoadingCache<String, Integer> mapKeys = Caffeine.newBuilder()
|
||||
.build(this::findOrCreateMapKey);
|
||||
final LoadingCache<Compression, Integer> compressionKeys = Caffeine.newBuilder()
|
||||
protected final LoadingCache<Compression, Integer> compressionKeys = Caffeine.newBuilder()
|
||||
.build(this::findOrCreateCompressionKey);
|
||||
protected final LoadingCache<Key, Integer> itemStorageKeys = Caffeine.newBuilder()
|
||||
.build(this::findOrCreateItemStorageKey);
|
||||
protected final LoadingCache<Key, Integer> 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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 (?)
|
||||
""";
|
||||
}
|
||||
|
@ -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 (?)
|
||||
""";
|
||||
}
|
||||
|
@ -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 (?)
|
||||
""";
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package de.bluecolored.bluemap.core.util;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BiIntConsumer {
|
||||
|
||||
void accept(int a, int b);
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Vector2i> 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<Vector2i> getIntersecting(Vector2i cell, Grid targetGrid) {
|
||||
Vector2i min = getCellMin(cell, targetGrid);
|
||||
Vector2i max = getCellMax(cell, targetGrid);
|
||||
|
||||
if (min.equals(max)) return Collections.singleton(min);
|
||||
|
||||
Collection<Vector2i> intersects = new ArrayList<>();
|
||||
Collection<Vector2i> 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));
|
||||
|
@ -34,7 +34,7 @@ public class Lazy<T> {
|
||||
private Supplier<T> loader;
|
||||
|
||||
@DebugDump
|
||||
private T value;
|
||||
private volatile T value;
|
||||
|
||||
public Lazy(Supplier<T> 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;
|
||||
|
@ -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<T> implements TypeAdapter<T[]> {
|
||||
|
||||
private final Class<T> type;
|
||||
private final TypeAdapter<T[]> paletteAdapter;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public PalettedArrayAdapter(BlueNBT blueNBT, Class<T> type) {
|
||||
this.type = type;
|
||||
this.paletteAdapter = ArrayAdapterFactory.INSTANCE.create((TypeToken<T[]>) 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<T, Byte> 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();
|
||||
}
|
||||
|
||||
}
|
@ -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<T extends Keyed> {
|
||||
|
||||
private final ConcurrentHashMap<Key, T> entries;
|
||||
private final ConcurrentHashMap<Key, T> entries = new ConcurrentHashMap<>();
|
||||
|
||||
public Registry() {
|
||||
this.entries = new ConcurrentHashMap<>();
|
||||
}
|
||||
private final Set<Key> keys = Collections.unmodifiableSet(entries.keySet());
|
||||
private final Collection<T> 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<Key> keys() {
|
||||
return Collections.unmodifiableSet(entries.keySet());
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable collection of entries in this registry
|
||||
*/
|
||||
public Collection<T> values() {
|
||||
return Collections.unmodifiableCollection(entries.values());
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<T extends Keyed> implements TypeAdapter<T> {
|
||||
|
||||
private final Registry<T> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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; }
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)}.<br>
|
||||
* Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, int)}.<br>
|
||||
* And if (any only if) that method returned <code>true</code>, 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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user