Skip to content

Commit

Permalink
Improve the UX for when the config file is corrupt
Browse files Browse the repository at this point in the history
  • Loading branch information
jellysquid3 committed Jan 26, 2024
1 parent ff7464d commit 7ad9414
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package me.jellysquid.mods.sodium.client;

import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.console.Console;
import me.jellysquid.mods.sodium.client.gui.console.message.MessageLevel;
import me.jellysquid.mods.sodium.client.util.FlawlessFrames;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.text.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -50,12 +53,14 @@ public static Logger logger() {

private static SodiumGameOptions loadConfig() {
try {
return SodiumGameOptions.load();
return SodiumGameOptions.loadFromDisk();
} catch (Exception e) {
LOGGER.error("Failed to load configuration file", e);
LOGGER.error("Using default configuration file in read-only mode");

var config = new SodiumGameOptions();
Console.instance().logMessage(MessageLevel.SEVERE, Text.translatable("sodium.console.config_not_loaded"), 12.5);

var config = SodiumGameOptions.defaults();
config.setReadOnly();

return config;
Expand All @@ -66,7 +71,7 @@ public static void restoreDefaultOptions() {
CONFIG = SodiumGameOptions.defaults();

try {
CONFIG.writeChanges();
SodiumGameOptions.writeToDisk(CONFIG);
} catch (IOException e) {
throw new RuntimeException("Failed to write config file", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ public class SodiumGameOptions {

private boolean readOnly;

private Path configPath;
private SodiumGameOptions() {
// NO-OP
}

public static SodiumGameOptions defaults() {
var options = new SodiumGameOptions();
options.configPath = getConfigPath(DEFAULT_FILE_NAME);

return options;
return new SodiumGameOptions();
}

public static class PerformanceSettings {
Expand Down Expand Up @@ -92,12 +91,8 @@ public boolean isFancy(GraphicsMode graphicsMode) {
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.create();

public static SodiumGameOptions load() {
return load(DEFAULT_FILE_NAME);
}

public static SodiumGameOptions load(String name) {
Path path = getConfigPath(name);
public static SodiumGameOptions loadFromDisk() {
Path path = getConfigPath();
SodiumGameOptions config;

if (Files.exists(path)) {
Expand All @@ -110,29 +105,28 @@ public static SodiumGameOptions load(String name) {
config = new SodiumGameOptions();
}

config.configPath = path;

try {
config.writeChanges();
writeToDisk(config);
} catch (IOException e) {
throw new RuntimeException("Couldn't update config file", e);
}

return config;
}

private static Path getConfigPath(String name) {
private static Path getConfigPath() {
return FabricLoader.getInstance()
.getConfigDir()
.resolve(name);
.resolve(DEFAULT_FILE_NAME);
}

public void writeChanges() throws IOException {
if (this.isReadOnly()) {
public static void writeToDisk(SodiumGameOptions config) throws IOException {
if (config.isReadOnly()) {
throw new IllegalStateException("Config file is read-only");
}

Path dir = this.configPath.getParent();
Path path = getConfigPath();
Path dir = path.getParent();

if (!Files.exists(dir)) {
Files.createDirectories(dir);
Expand All @@ -141,13 +135,13 @@ public void writeChanges() throws IOException {
}

// Use a temporary location next to the config's final destination
Path tempPath = this.configPath.resolveSibling(this.configPath.getFileName() + ".tmp");
Path tempPath = path.resolveSibling(path.getFileName() + ".tmp");

// Write the file to our temporary location
Files.writeString(tempPath, GSON.toJson(this));
Files.writeString(tempPath, GSON.toJson(config));

// Atomically replace the old config file (if it exists) with the temporary file
Files.move(tempPath, this.configPath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
Files.move(tempPath, path, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
}

public boolean isReadOnly() {
Expand All @@ -157,8 +151,4 @@ public boolean isReadOnly() {
public void setReadOnly() {
this.readOnly = true;
}

public String getFileName() {
return this.configPath.getFileName().toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
import me.jellysquid.mods.sodium.client.gui.options.control.Control;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage;
import me.jellysquid.mods.sodium.client.gui.screen.ConfigCorruptedScreen;
import me.jellysquid.mods.sodium.client.gui.widgets.FlatButtonWidget;
import me.jellysquid.mods.sodium.client.util.Dim2i;
import net.caffeinemc.mods.sodium.api.util.ColorMixer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.option.VideoOptionsScreen;
import net.minecraft.text.OrderedText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Language;
Expand Down Expand Up @@ -44,8 +43,8 @@ public class SodiumOptionsGUI extends Screen {
private boolean hasPendingChanges;
private ControlElement<?> hoveredElement;

public SodiumOptionsGUI(Screen prevScreen) {
super(Text.translatable("Sodium Options"));
private SodiumOptionsGUI(Screen prevScreen) {
super(Text.literal("Sodium Renderer Settings"));

this.prevScreen = prevScreen;

Expand All @@ -55,6 +54,14 @@ public SodiumOptionsGUI(Screen prevScreen) {
this.pages.add(SodiumGameOptionPages.advanced());
}

public static Screen createScreen(Screen currentScreen) {
if (SodiumClientMod.options().isReadOnly()) {
return new ConfigCorruptedScreen(currentScreen, SodiumOptionsGUI::new);
} else {
return new SodiumOptionsGUI(currentScreen);
}
}

public void setPage(OptionPage page) {
this.currentPage = page;

Expand Down Expand Up @@ -112,7 +119,7 @@ private void hideDonationButton() {
options.notifications.hideDonationButton = true;

try {
options.writeChanges();
SodiumGameOptions.writeToDisk(options);
} catch (IOException e) {
throw new RuntimeException("Failed to save configuration", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public SodiumGameOptions getData() {
@Override
public void save() {
try {
this.options.writeChanges();
SodiumGameOptions.writeToDisk(this.options);
} catch (IOException e) {
throw new RuntimeException("Couldn't save configuration changes", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package me.jellysquid.mods.sodium.client.gui.screen;

import me.jellysquid.mods.sodium.client.SodiumClientMod;
import me.jellysquid.mods.sodium.client.gui.console.Console;
import me.jellysquid.mods.sodium.client.gui.console.message.MessageLevel;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

Expand All @@ -18,9 +22,9 @@ public class ConfigCorruptedScreen extends Screen {
can happen when the file has been corrupted on disk, or when trying
to manually edit the file by hand.
We can attempt to fix this problem automatically by restoring the
config file back to known-good defaults, but you will lose any
changes that have since been made to your video settings.
If you continue, the configuration file will be reset back to known-good
defaults, and you will lose any changes that have since been made to your
Video Settings.
More information about the error can be found in the log file.
""";
Expand All @@ -29,37 +33,45 @@ public class ConfigCorruptedScreen extends Screen {
.map(Text::literal)
.collect(Collectors.toList());

private static final Text TEXT_BUTTON_RESTORE_DEFAULTS = Text.literal("Restore defaults");
private static final Text TEXT_BUTTON_CLOSE_GAME = Text.literal("Close game");
private static final int BUTTON_WIDTH = 140;
private static final int BUTTON_HEIGHT = 20;

private final Supplier<Screen> child;
private static final int SCREEN_PADDING = 32;

public ConfigCorruptedScreen(Supplier<Screen> child) {
super(Text.literal("Config corruption detected"));
private final @Nullable Screen prevScreen;
private final Function<Screen, Screen> nextScreen;

this.child = child;
public ConfigCorruptedScreen(@Nullable Screen prevScreen, @Nullable Function<Screen, Screen> nextScreen) {
super(Text.literal("Sodium failed to load the configuration file"));

this.prevScreen = prevScreen;
this.nextScreen = nextScreen;
}

@Override
protected void init() {
super.init();

this.addDrawableChild(ButtonWidget.builder(TEXT_BUTTON_RESTORE_DEFAULTS, (btn) -> {
int buttonY = this.height - SCREEN_PADDING - BUTTON_HEIGHT;

this.addDrawableChild(ButtonWidget.builder(Text.literal("Continue"), (btn) -> {
Console.instance().logMessage(MessageLevel.INFO, Text.translatable("sodium.console.config_file_was_reset"), 3.0);

SodiumClientMod.restoreDefaultOptions();
MinecraftClient.getInstance().setScreen(this.child.get());
}).dimensions(32, this.height - 40, 174, 20).build());
MinecraftClient.getInstance().setScreen(this.nextScreen.apply(this.prevScreen));
}).dimensions(this.width - SCREEN_PADDING - BUTTON_WIDTH, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT).build());

this.addDrawableChild(ButtonWidget.builder(TEXT_BUTTON_CLOSE_GAME, (btn) -> {
MinecraftClient.getInstance().scheduleStop();
}).dimensions(this.width - 174 - 32, this.height - 40, 174, 20).build());
this.addDrawableChild(ButtonWidget.builder(Text.literal("Go back"), (btn) -> {
MinecraftClient.getInstance().setScreen(this.prevScreen);
}).dimensions(SCREEN_PADDING, buttonY, BUTTON_WIDTH, BUTTON_HEIGHT).build());
}

@Override
public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) {
super.render(drawContext, mouseX, mouseY, delta);

drawContext.drawTextWithShadow(this.textRenderer, Text.literal("Sodium Renderer"), 32, 32, 0xffffff);
drawContext.drawTextWithShadow(this.textRenderer, Text.literal("Could not load configuration file"), 32, 48, 0xff0000);
drawContext.drawTextWithShadow(this.textRenderer, Text.literal("Could not load the configuration file"), 32, 48, 0xff0000);

for (int i = 0; i < TEXT_BODY.size(); i++) {
if (TEXT_BODY.get(i).getString().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ public class MinecraftClientMixin {
@Unique
private final LongArrayFIFOQueue fences = new LongArrayFIFOQueue();

@Inject(method = "<init>", at = @At("RETURN"))
private void postInit(RunArgs args, CallbackInfo ci) {
if (SodiumClientMod.options().isReadOnly()) {
var parent = MinecraftClient.getInstance().currentScreen;
MinecraftClient.getInstance().setScreen(new ConfigCorruptedScreen(() -> parent));
}
}

/**
* We run this at the beginning of the frame (except for the first frame) to give the previous frame plenty of time
* to render on the GPU. This allows us to stall on ClientWaitSync for less time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ protected OptionsScreenMixin(Text title) {
@Dynamic
@Inject(method = "method_19828", at = @At("HEAD"), cancellable = true)
private void open(CallbackInfoReturnable<Screen> ci) {
ci.setReturnValue(new SodiumOptionsGUI(this));
ci.setReturnValue(SodiumOptionsGUI.createScreen(this));
}
}
4 changes: 3 additions & 1 deletion src/main/resources/assets/sodium/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,7 @@
"sodium.console.pojav_launcher": "PojavLauncher is not supported when using Sodium.\n * You are very likely to run into extreme performance issues, graphical bugs, and crashes.\n * You will be on your own if you decide to continue -- we will not help you with any bugs or crashes!",
"sodium.console.core_shaders_error": "The following resource packs are incompatible with Sodium:",
"sodium.console.core_shaders_warn": "The following resource packs may be incompatible with Sodium:",
"sodium.console.core_shaders_info": "Check the game log for detailed information."
"sodium.console.core_shaders_info": "Check the game log for detailed information.",
"sodium.console.config_not_loaded": "The configuration file for Sodium has been corrupted, or is currently unreadable. Some options have been temporarily reset to their defaults. Please open the Video Settings screen to resolve this problem.",
"sodium.console.config_file_was_reset": "The config file has been reset to known-good defaults."
}

0 comments on commit 7ad9414

Please sign in to comment.