Configuration Patterns
Běžné vzory pro práci s konfigurací v Hytale pluginech.
---
Singleton Config Manager
Základní Vzor
public class ConfigManager {
private static ConfigManager instance; private final Path configPath;
private final ReentrantLock saveLock = new ReentrantLock();
private final AtomicBoolean pendingSave = new AtomicBoolean(false);
private BsonDocument config;
private ConfigManager(Path dataFolder) {
this.configPath = dataFolder.resolve("config.json");
}
public static void init(Path dataFolder) {
instance = new ConfigManager(dataFolder);
}
public static ConfigManager get() {
return instance;
}
// Gettery a settery pro konfigurace...
}
---
Načítání při Startu
S AllWorldsLoadedEvent
@Override
protected void setup() {
// Inicializuj config manager
ConfigManager.init(getDataFolder()); // Načti konfiguraci po načtení světů
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, event -> {
ConfigManager.get().load().join();
getLogger().atInfo().log("Configuration loaded");
});
}
Asynchronní Načítání
public CompletableFuture load() {
return BsonUtil.readDocument(configPath).thenAccept(doc -> {
if (doc != null) {
this.config = doc;
validateAndMigrate();
} else {
getLogger().atInfo().log("Config not found, creating defaults");
this.config = createDefaults();
save();
}
}).exceptionally(ex -> {
getLogger().at(Level.SEVERE).withCause(ex).log("Failed to load config:");
this.config = createDefaults();
return null;
});
}
---
Výchozí Hodnoty
Vytvoření Výchozí Konfigurace
private BsonDocument createDefaults() {
BsonDocument doc = new BsonDocument(); // Verze konfigurace (pro migraci)
doc.put("Version", new BsonInt32(1));
// Základní nastavení
doc.put("Debug", new BsonBoolean(false));
doc.put("Language", new BsonString("en-US"));
doc.put("MaxWarps", new BsonInt32(100));
// Vnořené nastavení
BsonDocument database = new BsonDocument();
database.put("Type", new BsonString("sqlite"));
database.put("Path", new BsonString("data/database.db"));
doc.put("Database", database);
// Seznamy
BsonArray adminUuids = new BsonArray();
doc.put("AdminUuids", adminUuids);
return doc;
}
Bezpečné Gettery s Výchozími Hodnotami
public boolean isDebug() {
return config.containsKey("Debug")
? config.getBoolean("Debug").getValue()
: false; // Výchozí hodnota
}public int getMaxWarps() {
return config.containsKey("MaxWarps")
? config.getInt32("MaxWarps").getValue()
: 100; // Výchozí hodnota
}
public String getLanguage() {
return config.containsKey("Language")
? config.getString("Language").getValue()
: "en-US"; // Výchozí hodnota
}
---
Thread-Safe Ukládání
Z TeleportPlugin - Save Lock Pattern
private final ReentrantLock saveLock = new ReentrantLock();
private final AtomicBoolean postSaveRedo = new AtomicBoolean(false);public void save() {
if (saveLock.tryLock()) {
try {
doSave();
} catch (Throwable e) {
getLogger().at(Level.SEVERE).withCause(e).log("Failed to save:");
} finally {
saveLock.unlock();
}
// Pokud někdo chtěl uložit během ukládání
if (postSaveRedo.getAndSet(false)) {
save(); // Rekurzivní volání
}
} else {
// Někdo jiný ukládá, označíme pro opakování
postSaveRedo.set(true);
}
}
private void doSave() {
BsonUtil.writeDocument(configPath, config).join();
getLogger().atInfo().log("Configuration saved");
}
Debounced Save
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture> pendingSaveFuture;public synchronized void scheduleSave() {
// Zruš předchozí naplánované uložení
if (pendingSaveFuture != null && !pendingSaveFuture.isDone()) {
pendingSaveFuture.cancel(false);
}
// Naplánuj nové uložení za 5 sekund
pendingSaveFuture = scheduler.schedule(this::doSave, 5, TimeUnit.SECONDS);
}
public void setValue(String key, BsonValue value) {
config.put(key, value);
scheduleSave(); // Automatické uložení po změně
}
---
Migrace Konfigurace
Verzovaná Konfigurace
private void validateAndMigrate() {
int version = config.containsKey("Version")
? config.getInt32("Version").getValue()
: 0; if (version < 1) {
migrateToV1();
}
if (version < 2) {
migrateToV2();
}
// Aktualizuj verzi
config.put("Version", new BsonInt32(2));
}
private void migrateToV1() {
getLogger().atInfo().log("Migrating config to v1...");
// Přejmenování klíčů
if (config.containsKey("debug")) {
config.put("Debug", config.get("debug"));
config.remove("debug");
}
// Přidání nových klíčů
if (!config.containsKey("Language")) {
config.put("Language", new BsonString("en-US"));
}
}
private void migrateToV2() {
getLogger().atInfo().log("Migrating config to v2...");
// Změna struktury
if (!config.containsKey("Database")) {
BsonDocument database = new BsonDocument();
database.put("Type", new BsonString("sqlite"));
database.put("Path", new BsonString("data/database.db"));
config.put("Database", database);
}
}
---
Validace
Validace Hodnot
public void validate() throws ConfigException {
// Povinné klíče
requireKey("Version");
requireKey("Language"); // Validace hodnot
int maxWarps = getMaxWarps();
if (maxWarps < 0 || maxWarps > 10000) {
throw new ConfigException("MaxWarps must be between 0 and 10000");
}
String language = getLanguage();
if (!isValidLanguage(language)) {
throw new ConfigException("Invalid language: " + language);
}
}
private void requireKey(String key) throws ConfigException {
if (!config.containsKey(key)) {
throw new ConfigException("Missing required config key: " + key);
}
}
private boolean isValidLanguage(String lang) {
return lang != null && lang.matches("[a-z]{2}-[A-Z]{2}");
}
---
Reload Konfigurace
Hot Reload
public CompletableFuture reload() {
getLogger().atInfo().log("Reloading configuration..."); return BsonUtil.readDocument(configPath).thenAccept(doc -> {
if (doc != null) {
BsonDocument oldConfig = this.config;
this.config = doc;
// Notifikuj o změnách
notifyConfigChanged(oldConfig, doc);
getLogger().atInfo().log("Configuration reloaded");
} else {
getLogger().atWarning().log("Failed to reload: file not found");
}
});
}
private void notifyConfigChanged(BsonDocument oldConfig, BsonDocument newConfig) {
// Porovnej změny
if (!Objects.equals(oldConfig.get("Debug"), newConfig.get("Debug"))) {
boolean debug = getBoolean(newConfig, "Debug", false);
getLogger().atInfo().log("Debug mode changed to: %s", debug);
}
// Další reakce na změny...
}
Reload Command
public class ReloadCommand extends CommandBase { public ReloadCommand() {
super("reload", "myplugin.commands.reload.desc");
requirePermission(HytalePermissions.of("myplugin.admin.reload"));
}
@Override
public void executeSync(CommandContext context) {
ConfigManager.get().reload().thenRun(() -> {
context.sendMessage(Message.raw("Configuration reloaded!").color("#00FF00"));
}).exceptionally(ex -> {
context.sendMessage(Message.raw("Failed to reload: " + ex.getMessage()).color("#FF0000"));
return null;
});
}
}
---
Perzistence při Shutdown
Graceful Shutdown
@Override
protected void shutdown() {
getLogger().atInfo().log("Saving configuration before shutdown..."); try {
// Synchronní save při shutdown
BsonUtil.writeDocument(configPath, config, false).get(10, TimeUnit.SECONDS);
getLogger().atInfo().log("Configuration saved successfully");
} catch (Exception e) {
getLogger().at(Level.SEVERE).withCause(e).log("Failed to save configuration:");
}
// Cleanup scheduler
if (scheduler != null) {
scheduler.shutdown();
try {
scheduler.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
---
Per-Player Konfigurace
Player Settings
public class PlayerSettings {
private final Map playerSettings = new ConcurrentHashMap<>();
private final Path settingsDir; public PlayerSettings(Path dataFolder) {
this.settingsDir = dataFolder.resolve("players");
}
public CompletableFuture getSettings(UUID uuid) {
BsonDocument cached = playerSettings.get(uuid);
if (cached != null) {
return CompletableFuture.completedFuture(cached);
}
Path path = settingsDir.resolve(uuid.toString() + ".json");
return BsonUtil.readDocument(path).thenApply(doc -> {
if (doc == null) {
doc = createDefaultPlayerSettings();
}
playerSettings.put(uuid, doc);
return doc;
});
}
public void saveSettings(UUID uuid) {
BsonDocument doc = playerSettings.get(uuid);
if (doc != null) {
Path path = settingsDir.resolve(uuid.toString() + ".json");
BsonUtil.writeDocument(path, doc);
}
}
private BsonDocument createDefaultPlayerSettings() {
BsonDocument doc = new BsonDocument();
doc.put("TeleportRequests", new BsonBoolean(true));
doc.put("PrivateMessages", new BsonBoolean(true));
return doc;
}
}
---
Kompletní Příklad
public class MyPlugin extends JavaPlugin {
private static MyPlugin instance;
private ConfigManager configManager; public MyPlugin(JavaPluginInit init) {
super(init);
}
public static MyPlugin get() {
return instance;
}
@Override
protected void setup() {
instance = this;
// Inicializuj config manager
configManager = new ConfigManager(getDataFolder());
// Načti konfiguraci po načtení světů
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, event -> {
configManager.load().join();
});
// Registruj reload command
getCommandRegistry().registerCommand(new ReloadConfigCommand());
}
@Override
protected void shutdown() {
// Ulož konfiguraci
configManager.saveSync();
}
public ConfigManager getConfigManager() {
return configManager;
}
}
---
Shrnutí
| Pattern | Použití |
|---------|---------|
| Singleton | Globální přístup ke konfiguraci |
| Lock Pattern | Thread-safe ukládání |
| Debounced Save | Seskupení častých změn |
| Migrace | Zpětná kompatibilita |
| Hot Reload | Změna konfigurace bez restartu |
| Per-Player | Uživatelská nastavení |
| Best Practice | Popis |
|---------------|-------|
| Výchozí hodnoty | Vždy mít fallback hodnoty |
| Validace | Kontrolovat hodnoty při načítání |
| Verzování | Umožnit budoucí migrace |
| Async I/O | Neblokovat hlavní vlákno |
| Sync Shutdown | Uložit synchronně při vypínání |