Improve world config management with parsing of new and removed worlds on reload

This commit is contained in:
Ben Woo 2023-09-09 00:41:36 +08:00
parent b4617c1c1f
commit 5c7c9ba465
No known key found for this signature in database
GPG Key ID: FB2A3645536E12C8
7 changed files with 123 additions and 54 deletions

View File

@ -30,6 +30,7 @@ import io.vavr.control.Try;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import jakarta.inject.Provider; import jakarta.inject.Provider;
import me.main__.util.SerializationConfig.SerializationConfig; import me.main__.util.SerializationConfig.SerializationConfig;
import org.bukkit.Bukkit;
import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -115,7 +116,10 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
Logging.setShowingConfig(shouldShowConfig()); Logging.setShowingConfig(shouldShowConfig());
// Initialize the worlds // Initialize the worlds
worldManagerProvider.get().initAllWorlds(); worldManagerProvider.get().initAllWorlds().onFailure(e -> {
Logging.severe("Failed to initialize worlds");
e.printStackTrace();
});
// Setup economy here so vault is loaded // Setup economy here so vault is loaded
loadEconomist(); loadEconomist();

View File

@ -52,7 +52,7 @@ public class ReloadCommand extends MultiverseCommand {
try { try {
// TODO: Make this all Try<Void> // TODO: Make this all Try<Void>
this.config.load().getOrElseThrow(e -> new RuntimeException("Failed to load config", e)); this.config.load().getOrElseThrow(e -> new RuntimeException("Failed to load config", e));
this.worldManager.initAllWorlds(); this.worldManager.initAllWorlds().getOrElseThrow(e -> new RuntimeException("Failed to init worlds", e));
this.anchorManager.loadAnchors(); this.anchorManager.loadAnchors();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -93,37 +93,40 @@ public class WorldManager {
/** /**
* Loads all worlds from the worlds config. * Loads all worlds from the worlds config.
*
* @return The result of the load.
*/ */
public void initAllWorlds() { public Try<Void> initAllWorlds() {
if (!populateWorldFromConfig()) { return populateWorldFromConfig().andThenTry(() -> {
return; loadDefaultWorlds();
} autoLoadWorlds();
loadDefaultWorlds(); saveWorldsConfig();
autoLoadWorlds(); });
saveWorldsConfig();
} }
/** /**
* Generate worlds from the worlds config. * Populate world map from the worlds.yml config.
*
* @return The result of the world map population.
*/ */
private boolean populateWorldFromConfig() { private Try<Void> populateWorldFromConfig() {
Try<Void> load = worldsConfigManager.load(); return worldsConfigManager.load().mapTry(result -> {
if (load.isFailure()) { var newWorldConfigs = result._1();
Logging.severe("Failed to load worlds config: " + load.getCause().getMessage()); var removedWorlds = result._2();
load.getCause().printStackTrace();
return false; newWorldConfigs.forEach(worldConfig -> getWorld(worldConfig.getWorldName())
}
worldsConfigManager.getAllWorldConfigs().forEach(worldConfig -> {
getLoadedWorld(worldConfig.getWorldName())
.peek(loadedWorld -> loadedWorld.setWorldConfig(worldConfig));
getWorld(worldConfig.getWorldName())
.peek(unloadedWorld -> unloadedWorld.setWorldConfig(worldConfig)) .peek(unloadedWorld -> unloadedWorld.setWorldConfig(worldConfig))
.onEmpty(() -> { .onEmpty(() -> {
MultiverseWorld mvWorld = new MultiverseWorld(worldConfig.getWorldName(), worldConfig); MultiverseWorld mvWorld = new MultiverseWorld(worldConfig.getWorldName(), worldConfig);
worldsMap.put(mvWorld.getName(), mvWorld); worldsMap.put(mvWorld.getName(), mvWorld);
}); }));
removedWorlds.forEach(worldName -> removeWorld(worldName)
.onFailure(failure -> Logging.severe("Failed to unload world %s: %s", worldName, failure))
.onSuccess(success -> Logging.fine("Unloaded world %s as it was removed from config", worldName)));
return null;
}); });
return true;
} }
/** /**
@ -361,7 +364,7 @@ public class WorldManager {
replace("{world}").with(world.getName())); replace("{world}").with(world.getName()));
}, },
mvWorld -> { mvWorld -> {
Logging.fine("Removed MVWorld from map: " + world.getName()); Logging.fine("Removed MultiverseWorld from map: " + world.getName());
mvWorld.getWorldConfig().deferenceMVWorld(); mvWorld.getWorldConfig().deferenceMVWorld();
return Result.success(UnloadWorldResult.Success.UNLOADED, return Result.success(UnloadWorldResult.Success.UNLOADED,
replace("{world}").with(world.getName())); replace("{world}").with(world.getName()));

View File

@ -38,6 +38,10 @@ public final class WorldConfig {
return configHandle.load(); return configHandle.load();
} }
public Try<Void> load(ConfigurationSection section) {
return configHandle.load(section);
}
public String getWorldName() { public String getWorldName() {
return worldName; return worldName;
} }

View File

@ -1,22 +1,30 @@
package com.onarandombox.MultiverseCore.worldnew.config; package com.onarandombox.MultiverseCore.worldnew.config;
import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.MultiverseCore;
import io.vavr.Tuple2;
import io.vavr.control.Try; import io.vavr.control.Try;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service; import org.jvnet.hk2.annotations.Service;
import java.io.File; import java.io.File;
import java.util.Collection; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
/**
* Manages the worlds.yml file.
*/
@Service @Service
public final class WorldsConfigManager { public final class WorldsConfigManager {
private static final String CONFIG_FILENAME = "worlds2.yml"; private static final String CONFIG_FILENAME = "worlds2.yml"; // TODO: Rename to worlds.yml
private final Map<String, WorldConfig> worldConfigMap; private final Map<String, WorldConfig> worldConfigMap;
private final File worldConfigFile; private final File worldConfigFile;
@ -28,46 +36,88 @@ public final class WorldsConfigManager {
worldConfigFile = core.getDataFolder().toPath().resolve(CONFIG_FILENAME).toFile(); worldConfigFile = core.getDataFolder().toPath().resolve(CONFIG_FILENAME).toFile();
} }
public Try<Void> load() { /**
worldConfigMap.clear(); * Loads the worlds.yml file and creates a WorldConfig for each world in the file if it doesn't already exist.
*
return Try.run(() -> { * @return A tuple containing a list of the new WorldConfigs added and a list of the worlds removed from the config.
if (!worldConfigFile.exists()) { */
worldConfigFile.createNewFile(); public Try<Tuple2<List<WorldConfig>, List<String>>> load() {
} return Try.of(() -> {
worldsConfig = new YamlConfiguration(); loadWorldYmlFile();
worldsConfig.load(worldConfigFile); return parseNewAndRemovedWorlds();
}).andThenTry(() -> {
for (String worldName : getAllWorldsInConfig()) {
worldConfigMap.put(worldName, new WorldConfig(worldName, getWorldConfigSection(worldName)));
}
}).onFailure(e -> {
worldsConfig = null;
worldConfigMap.clear();
}); });
} }
private void loadWorldYmlFile() throws IOException, InvalidConfigurationException {
if (!worldConfigFile.exists() && !worldConfigFile.createNewFile()) {
throw new IllegalStateException("Could not create worlds.yml config file");
}
worldsConfig = new YamlConfiguration();
worldsConfig.load(worldConfigFile);
}
private Tuple2<List<WorldConfig>, List<String>> parseNewAndRemovedWorlds() {
Set<String> allWorldsInConfig = worldsConfig.getKeys(false);
List<WorldConfig> newWorldsAdded = new ArrayList<>();
for (String worldName : allWorldsInConfig) {
WorldConfig worldConfig = getWorldConfig(worldName);
if (worldConfig == null) {
WorldConfig newWorldConfig = new WorldConfig(worldName, getWorldConfigSection(worldName));
worldConfigMap.put(worldName, newWorldConfig);
newWorldsAdded.add(newWorldConfig);
} else {
worldConfig.load(getWorldConfigSection(worldName));
}
}
List<String> worldsRemoved = worldConfigMap.keySet().stream()
.filter(worldName -> !allWorldsInConfig.contains(worldName))
.toList();
for (String s : worldsRemoved) {
worldConfigMap.remove(s);
}
return new Tuple2<>(newWorldsAdded, worldsRemoved);
}
/**
* Whether the worlds.yml file has been loaded.
*
* @return Whether the worlds.yml file has been loaded.
*/
public boolean isLoaded() { public boolean isLoaded() {
return worldsConfig != null; return worldsConfig != null;
} }
/**
* Saves the worlds.yml file.
*
* @return Whether the save was successful or the error that occurred.
*/
public Try<Void> save() { public Try<Void> save() {
return Try.run(() -> worldsConfig.save(worldConfigFile)); return Try.run(() -> worldsConfig.save(worldConfigFile));
} }
public Set<String> getAllWorldsInConfig() { /**
return worldsConfig.getKeys(false); * Gets the {@link WorldConfig} instance of all worlds in the worlds.yml file.
} *
* @param worldName The name of the world to check.
public Collection<WorldConfig> getAllWorldConfigs() { * @return Whether the worlds.yml file contains the given world.
return worldConfigMap.values(); */
} public @Nullable WorldConfig getWorldConfig(@NotNull String worldName) {
public WorldConfig getWorldConfig(String worldName) {
return worldConfigMap.get(worldName); return worldConfigMap.get(worldName);
} }
public WorldConfig addWorldConfig(String worldName) { /**
* Add a new world to the worlds.yml file. If a world with the given name already exists, an exception is thrown.
*
* @param worldName The name of the world to add.
* @return The newly created {@link WorldConfig} instance.
*/
public @NotNull WorldConfig addWorldConfig(@NotNull String worldName) {
if (worldConfigMap.containsKey(worldName)) { if (worldConfigMap.containsKey(worldName)) {
throw new IllegalArgumentException("WorldConfig for world " + worldName + " already exists."); throw new IllegalArgumentException("WorldConfig for world " + worldName + " already exists.");
} }
@ -76,7 +126,12 @@ public final class WorldsConfigManager {
return worldConfig; return worldConfig;
} }
public void deleteWorldConfig(String worldName) { /**
* Deletes the world config for the given world.
*
* @param worldName The name of the world to delete.
*/
public void deleteWorldConfig(@NotNull String worldName) {
worldConfigMap.remove(worldName); worldConfigMap.remove(worldName);
worldsConfig.set(worldName, null); worldsConfig.set(worldName, null);
} }

View File

@ -48,6 +48,8 @@ class WorldConfigMangerTest : TestWithMockBukkit() {
@Test @Test
fun `Updating existing world properties`() { fun `Updating existing world properties`() {
val worldConfig = worldConfigManager.getWorldConfig("world") val worldConfig = worldConfigManager.getWorldConfig("world")
assertNotNull(worldConfig)
worldConfig.setProperty("adjust-spawn", true) worldConfig.setProperty("adjust-spawn", true)
worldConfig.setProperty("alias", "newalias") worldConfig.setProperty("alias", "newalias")
worldConfig.setProperty("spawn-location", SpawnLocation(-64.0, 64.0, 48.0)) worldConfig.setProperty("spawn-location", SpawnLocation(-64.0, 64.0, 48.0))

View File

@ -29,7 +29,8 @@ class WorldConfigTest : TestWithMockBukkit() {
throw IllegalStateException("WorldsConfigManager is not available as a service") } throw IllegalStateException("WorldsConfigManager is not available as a service") }
assertTrue(worldConfigManager.load().isSuccess) assertTrue(worldConfigManager.load().isSuccess)
worldConfig = worldConfigManager.getWorldConfig("world") worldConfig = worldConfigManager.getWorldConfig("world").takeIf { it != null } ?: run {
throw IllegalStateException("WorldConfig for world is not available") }
assertNotNull(worldConfig); assertNotNull(worldConfig);
} }