diff --git a/fabric/src/main/java/com/jozufozu/flywheel/impl/FabricFlwConfig.java b/fabric/src/main/java/com/jozufozu/flywheel/impl/FabricFlwConfig.java index 0c2bc0797..f8ec8ff37 100644 --- a/fabric/src/main/java/com/jozufozu/flywheel/impl/FabricFlwConfig.java +++ b/fabric/src/main/java/com/jozufozu/flywheel/impl/FabricFlwConfig.java @@ -1,15 +1,48 @@ package com.jozufozu.flywheel.impl; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.file.Path; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.api.backend.Backend; import com.jozufozu.flywheel.api.backend.BackendManager; -// TODO: Fabric config +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.ResourceLocationException; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; + public class FabricFlwConfig implements FlwConfig { - public static final FabricFlwConfig INSTANCE = new FabricFlwConfig(); + public static final Path PATH = FabricLoader.getInstance() + .getConfigDir() + .resolve("flywheel.json"); + + public static final FabricFlwConfig INSTANCE = new FabricFlwConfig(PATH.toFile()); + + protected static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + public static final int WORKER_THREADS_DEFAULT = -1; + public static final int WORKER_THREADS_MAX = Runtime.getRuntime() + .availableProcessors(); + public static final boolean LIMIT_UPDATES_DEFAULT = true; public Backend backend = BackendManager.getDefaultBackend(); - public boolean limitUpdates = true; - public int workerThreads = -1; + public boolean limitUpdates = LIMIT_UPDATES_DEFAULT; + public int workerThreads = WORKER_THREADS_DEFAULT; + + private final File file; + + public FabricFlwConfig(File file) { + this.file = file; + } @Override public Backend backend() { @@ -25,4 +58,106 @@ public class FabricFlwConfig implements FlwConfig { public int workerThreads() { return workerThreads; } + + public void load() { + if (file.exists()) { + try (FileReader reader = new FileReader(file)) { + fromJson(JsonParser.parseReader(reader)); + } catch (Exception e) { + Flywheel.LOGGER.error("Could not load config from file '{}'", file.getAbsolutePath(), e); + } + } + // In case we found an error in the config file, immediately save to fix it. + save(); + } + + public void save() { + try (FileWriter writer = new FileWriter(file)) { + GSON.toJson(toJson(), writer); + } catch (Exception e) { + Flywheel.LOGGER.error("Could not save config to file '{}'", file.getAbsolutePath(), e); + } + } + + public void fromJson(JsonElement json) { + if (!(json instanceof JsonObject object)) { + backend = BackendManager.getDefaultBackend(); + limitUpdates = LIMIT_UPDATES_DEFAULT; + workerThreads = WORKER_THREADS_DEFAULT; + return; + } + + readBackend(object); + readLimitUpdates(object); + readWorkerThreads(object); + } + + private void readBackend(JsonObject object) { + var backendJson = object.get("backend"); + + String err = null; + + if (backendJson instanceof JsonPrimitive primitive && primitive.isString()) { + var backendString = primitive.getAsString(); + try { + this.backend = Backend.REGISTRY.getOrThrow(new ResourceLocation(backendString)); + return; + } catch (IllegalArgumentException e) { + err = "backend ID '" + backendString + "' is not registered"; + } catch (ResourceLocationException e) { + err = "backend '" + backendString + "' is not a valid resource location"; + } catch (Exception e) { + // Something else went wrong? This should be dead code. + err = "backend '" + backendString + "' is invalid"; + } + } else if (backendJson != null) { + err = "backend must be a string"; + } + + // Don't log an error if the field is missing. + if (err != null) { + Flywheel.LOGGER.warn(err); + } + backend = BackendManager.getDefaultBackend(); + } + + private void readLimitUpdates(JsonObject object) { + var limitUpdatesJson = object.get("limitUpdates"); + if (limitUpdatesJson instanceof JsonPrimitive primitive && primitive.isBoolean()) { + limitUpdates = primitive.getAsBoolean(); + return; + } else if (limitUpdatesJson != null) { + Flywheel.LOGGER.warn("limitUpdates must be a boolean"); + } + limitUpdates = LIMIT_UPDATES_DEFAULT; + } + + private void readWorkerThreads(JsonObject object) { + var workerThreadsJson = object.get("workerThreads"); + + if (workerThreadsJson instanceof JsonPrimitive primitive && primitive.isNumber()) { + int configuredValue = primitive.getAsInt(); + + int clamped = Mth.clamp(configuredValue, WORKER_THREADS_DEFAULT, WORKER_THREADS_MAX); + + if (clamped != configuredValue) { + Flywheel.LOGGER.warn("workerThreads value of {} is out of range, clamping to {}", configuredValue, clamped); + } + + workerThreads = clamped; + return; + } else if (workerThreadsJson != null) { + Flywheel.LOGGER.warn("workerThreads must be an integer"); + } + + workerThreads = WORKER_THREADS_DEFAULT; + } + + public JsonObject toJson() { + JsonObject object = new JsonObject(); + object.addProperty("limitUpdates", limitUpdates); + object.addProperty("workerThreads", workerThreads); + object.addProperty("backend", Backend.REGISTRY.getIdOrThrow(backend).toString()); + return object; + } } diff --git a/fabric/src/main/java/com/jozufozu/flywheel/impl/FlwCommands.java b/fabric/src/main/java/com/jozufozu/flywheel/impl/FlwCommands.java index 7a40b1167..5e5f44dd2 100644 --- a/fabric/src/main/java/com/jozufozu/flywheel/impl/FlwCommands.java +++ b/fabric/src/main/java/com/jozufozu/flywheel/impl/FlwCommands.java @@ -25,8 +25,6 @@ public final class FlwCommands { } public static void registerClientCommands(CommandDispatcher dispatcher, CommandBuildContext buildContext) { - FabricFlwConfig config = FabricFlwConfig.INSTANCE; - LiteralArgumentBuilder command = ClientCommandManager.literal("flywheel"); command.then(ClientCommandManager.literal("backend") @@ -40,7 +38,8 @@ public final class FlwCommands { .then(ClientCommandManager.argument("id", BackendArgument.INSTANCE) .executes(context -> { Backend requestedBackend = context.getArgument("id", Backend.class); - config.backend = requestedBackend; + FabricFlwConfig.INSTANCE.backend = requestedBackend; + FabricFlwConfig.INSTANCE.save(); // Reload renderers so we can report the actual backend. Minecraft.getInstance().levelRenderer.allChanged(); @@ -60,7 +59,7 @@ public final class FlwCommands { command.then(ClientCommandManager.literal("limitUpdates") .executes(context -> { - if (config.limitUpdates) { + if (FabricFlwConfig.INSTANCE.limitUpdates) { context.getSource().sendFeedback(Component.translatable("command.flywheel.limit_updates.get.on")); } else { context.getSource().sendFeedback(Component.translatable("command.flywheel.limit_updates.get.off")); @@ -69,14 +68,16 @@ public final class FlwCommands { }) .then(ClientCommandManager.literal("on") .executes(context -> { - config.limitUpdates = true; + FabricFlwConfig.INSTANCE.limitUpdates = true; + FabricFlwConfig.INSTANCE.save(); context.getSource().sendFeedback(Component.translatable("command.flywheel.limit_updates.set.on")); Minecraft.getInstance().levelRenderer.allChanged(); return Command.SINGLE_SUCCESS; })) .then(ClientCommandManager.literal("off") .executes(context -> { - config.limitUpdates = false; + FabricFlwConfig.INSTANCE.limitUpdates = false; + FabricFlwConfig.INSTANCE.save(); context.getSource().sendFeedback(Component.translatable("command.flywheel.limit_updates.set.off")); Minecraft.getInstance().levelRenderer.allChanged(); return Command.SINGLE_SUCCESS; diff --git a/fabric/src/main/java/com/jozufozu/flywheel/impl/FlywheelFabric.java b/fabric/src/main/java/com/jozufozu/flywheel/impl/FlywheelFabric.java index 0885191ab..e4f58f5d8 100644 --- a/fabric/src/main/java/com/jozufozu/flywheel/impl/FlywheelFabric.java +++ b/fabric/src/main/java/com/jozufozu/flywheel/impl/FlywheelFabric.java @@ -44,6 +44,9 @@ public final class FlywheelFabric implements ClientModInitializer { // FIXME: Registries cannot be frozen this early. FlywheelInit.freezeRegistries(); + // Have to load the config after we freeze registries, + // so we can find third party backends. + FabricFlwConfig.INSTANCE.load(); } private static void setupImpl() {