Skip to content

Commit bef038b

Browse files
committed
Change config to only write non-default values to disk
Values that are missing from the config file are merged in from the default.
1 parent 5f35159 commit bef038b

File tree

6 files changed

+155
-117
lines changed

6 files changed

+155
-117
lines changed

platforms/common/src/main/java/dynamic_fps/impl/PowerState.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,37 @@ public enum PowerState {
99
/**
1010
* Window is currently focused.
1111
*/
12-
FOCUSED(false),
12+
FOCUSED(ConfigurabilityLevel.NONE),
1313

1414
/**
1515
* Mouse positioned over unfocused window.
1616
*/
17-
HOVERED(true),
17+
HOVERED(ConfigurabilityLevel.FULL),
1818

1919
/**
2020
* Another application is focused.
2121
*/
22-
UNFOCUSED(true),
22+
UNFOCUSED(ConfigurabilityLevel.FULL),
2323

2424
/**
2525
* Window minimized or otherwise hidden.
2626
*/
27-
INVISIBLE(true),
27+
INVISIBLE(ConfigurabilityLevel.FULL),
2828

2929
/**
3030
* User hasn't sent input for some time.
3131
*/
32-
ABANDONED(true);
32+
ABANDONED(ConfigurabilityLevel.FULL);
3333

34-
public final boolean configurable;
34+
public final ConfigurabilityLevel configurabilityLevel;
3535

36-
private PowerState(boolean configurable) {
37-
this.configurable = configurable;
36+
PowerState(ConfigurabilityLevel configurabilityLevel) {
37+
this.configurabilityLevel = configurabilityLevel;
38+
}
39+
40+
public enum ConfigurabilityLevel {
41+
NONE,
42+
SOME,
43+
FULL;
3844
}
3945
}

platforms/common/src/main/java/dynamic_fps/impl/compat/ClothConfig.java

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import dynamic_fps.impl.Constants;
44
import dynamic_fps.impl.DynamicFPSMod;
5+
import dynamic_fps.impl.config.DynamicFPSConfig;
56
import dynamic_fps.impl.config.option.GraphicsState;
67
import dynamic_fps.impl.PowerState;
78
import dynamic_fps.impl.config.Config;
@@ -34,12 +35,14 @@ public static Screen genConfigScreen(Screen parent) {
3435
localized("config", "category.general")
3536
);
3637

38+
DynamicFPSConfig defaultConfig = DynamicFPSConfig.DEFAULT;
39+
3740
general.addEntry(
3841
entryBuilder.startBooleanToggle(
3942
localized("config", "enabled"),
4043
DynamicFPSMod.modConfig.enabled()
4144
)
42-
.setDefaultValue(true)
45+
.setDefaultValue(defaultConfig.enabled())
4346
.setSaveConsumer(DynamicFPSMod.modConfig::setEnabled)
4447
.build()
4548
);
@@ -54,7 +57,7 @@ public static Screen genConfigScreen(Screen parent) {
5457
DynamicFPSMod.modConfig.idleTime() / 60,
5558
0, 30
5659
)
57-
.setDefaultValue(0)
60+
.setDefaultValue(defaultConfig.idleTime())
5861
.setSaveConsumer(value -> DynamicFPSMod.modConfig.setIdleTime(value * 60))
5962
.setTextGetter(ClothConfig::idleTimeMessage)
6063
.setTooltip(localized("config", "idle_time_tooltip"))
@@ -66,7 +69,7 @@ public static Screen genConfigScreen(Screen parent) {
6669
localized("config", "uncap_menu_frame_rate"),
6770
DynamicFPSMod.modConfig.uncapMenuFrameRate()
6871
)
69-
.setDefaultValue(false)
72+
.setDefaultValue(defaultConfig.uncapMenuFrameRate())
7073
.setSaveConsumer(DynamicFPSMod.modConfig::setUncapMenuFrameRate)
7174
.setTooltip(localized("config", "uncap_menu_frame_rate_tooltip"))
7275
.build()
@@ -76,40 +79,40 @@ public static Screen genConfigScreen(Screen parent) {
7679

7780
general.addEntry(
7881
entryBuilder.startIntSlider(
79-
localized("config", "volume_transition_speed_up"),
80-
volumeTransformer.toStep((int) (DynamicFPSMod.volumeTransitionSpeed().getUp() * 10)),
81-
1, 31
82-
)
83-
.setDefaultValue(volumeTransformer.toStep((int) (1.0f * 10)))
84-
.setSaveConsumer(step -> DynamicFPSMod.volumeTransitionSpeed().setUp((float) volumeTransformer.toValue(step) / 10))
85-
.setTextGetter(ClothConfig::volumeTransitionMessage)
86-
.setTooltip(localized("config", "volume_transition_speed_tooltip"))
87-
.build()
82+
localized("config", "volume_transition_speed_up"),
83+
volumeTransformer.toStep((int) (DynamicFPSMod.volumeTransitionSpeed().getUp() * 10)),
84+
1, 31
85+
)
86+
.setDefaultValue(volumeTransformer.toStep((int) (defaultConfig.volumeTransitionSpeed().getUp() * 10)))
87+
.setSaveConsumer(step -> DynamicFPSMod.volumeTransitionSpeed().setUp((float) volumeTransformer.toValue(step) / 10))
88+
.setTextGetter(ClothConfig::volumeTransitionMessage)
89+
.setTooltip(localized("config", "volume_transition_speed_tooltip"))
90+
.build()
8891
);
8992

9093
general.addEntry(
9194
entryBuilder.startIntSlider(
92-
localized("config", "volume_transition_speed_down"),
93-
volumeTransformer.toStep((int) (DynamicFPSMod.volumeTransitionSpeed().getDown() * 10)),
94-
1, 31
95-
)
96-
.setDefaultValue(volumeTransformer.toStep((int) (0.5f * 10)))
97-
.setSaveConsumer(step -> DynamicFPSMod.volumeTransitionSpeed().setDown((float) volumeTransformer.toValue(step) / 10))
98-
.setTextGetter(ClothConfig::volumeTransitionMessage)
99-
.setTooltip(localized("config", "volume_transition_speed_tooltip"))
100-
.build()
95+
localized("config", "volume_transition_speed_down"),
96+
volumeTransformer.toStep((int) (DynamicFPSMod.volumeTransitionSpeed().getDown() * 10)),
97+
1, 31
98+
)
99+
.setDefaultValue(volumeTransformer.toStep((int) (defaultConfig.volumeTransitionSpeed().getDown() * 10)))
100+
.setSaveConsumer(step -> DynamicFPSMod.volumeTransitionSpeed().setDown((float) volumeTransformer.toValue(step) / 10))
101+
.setTextGetter(ClothConfig::volumeTransitionMessage)
102+
.setTooltip(localized("config", "volume_transition_speed_tooltip"))
103+
.build()
101104
);
102105

103106
// Used for each state's frame rate target slider below
104107
VariableStepTransformer fpsTransformer = getFpsTransformer();
105108

106109
for (PowerState state : PowerState.values()) {
107-
if (!state.configurable) {
110+
if (state.configurabilityLevel == PowerState.ConfigurabilityLevel.NONE) {
108111
continue;
109112
}
110113

111114
Config config = DynamicFPSMod.modConfig.get(state);
112-
Config standard = Config.getDefault(state);
115+
Config standard = defaultConfig.get(state);
113116

114117
ConfigCategory category = builder.getOrCreateCategory(
115118
localized("config", "category." + state.toString().toLowerCase())
@@ -130,6 +133,11 @@ public static Screen genConfigScreen(Screen parent) {
130133
.build()
131134
);
132135

136+
// Further options are not allowed since this state is used while active.
137+
if (state.configurabilityLevel == PowerState.ConfigurabilityLevel.SOME) {
138+
continue;
139+
}
140+
133141
SubCategoryBuilder volumes = entryBuilder.startSubCategory(localized("config", "volume_multiplier"));
134142

135143
for (SoundSource source : SoundSource.values()) {

platforms/common/src/main/java/dynamic_fps/impl/config/Config.java

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -92,31 +92,4 @@ public boolean runGarbageCollector() {
9292
public void setRunGarbageCollector(boolean value) {
9393
this.runGarbageCollector = value;
9494
}
95-
96-
public static Config getDefault(PowerState state) {
97-
switch (state) {
98-
case HOVERED: {
99-
return new Config(60, withMasterVolume(1.0f), GraphicsState.DEFAULT, true, false);
100-
}
101-
case UNFOCUSED: {
102-
return new Config(1, withMasterVolume(0.25f), GraphicsState.DEFAULT, false, false);
103-
}
104-
case ABANDONED: {
105-
return new Config(10, withMasterVolume(1.0f), GraphicsState.DEFAULT, false, false);
106-
}
107-
case INVISIBLE: {
108-
return new Config(0, withMasterVolume(0.0f), GraphicsState.DEFAULT, false, false);
109-
}
110-
default: {
111-
throw new RuntimeException("Getting default configuration for unhandled power state " + state.toString());
112-
}
113-
}
114-
}
115-
116-
private static Map<String, Float> withMasterVolume(float value) {
117-
Map<String, Float> volumes = new HashMap<>();
118-
volumes.put(soundSourceName(SoundSource.MASTER), value);
119-
120-
return volumes;
121-
}
12295
}

platforms/common/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public final class DynamicFPSConfig {
1515
@SerializedName("states")
1616
private final Map<PowerState, Config> configs;
1717

18+
public static final DynamicFPSConfig DEFAULT = Serialization.loadDefault();
19+
1820
private DynamicFPSConfig(boolean enabled, int abandonTime, boolean uncapMenuFrameRate, VolumeTransitionSpeed volumeTransitionSpeed, Map<PowerState, Config> configs) {
1921
this.enabled = enabled;
2022
this.idleTime = abandonTime;
@@ -24,8 +26,8 @@ private DynamicFPSConfig(boolean enabled, int abandonTime, boolean uncapMenuFram
2426
this.configs = new EnumMap<>(configs);
2527

2628
for (PowerState state : PowerState.values()) {
27-
if (state.configurable) {
28-
this.configs.computeIfAbsent(state, Config::getDefault);
29+
if (state.configurabilityLevel != PowerState.ConfigurabilityLevel.NONE) {
30+
this.configs.computeIfAbsent(state, DEFAULT.configs::get);
2931
}
3032
}
3133
}

platforms/common/src/main/java/dynamic_fps/impl/config/Serialization.java

Lines changed: 63 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
import com.google.gson.JsonSerializationContext;
1414
import com.google.gson.JsonSerializer;
1515
import dynamic_fps.impl.Constants;
16-
import dynamic_fps.impl.PowerState;
1716
import dynamic_fps.impl.service.Platform;
1817
import dynamic_fps.impl.util.Logging;
1918
import org.jetbrains.annotations.Nullable;
2019

2120
import java.io.IOException;
21+
import java.io.InputStream;
2222
import java.lang.reflect.Type;
2323
import java.nio.charset.StandardCharsets;
2424
import java.nio.file.Files;
@@ -41,16 +41,19 @@ public class Serialization {
4141
private static final String CONFIG_FILE = Constants.MOD_ID + ".json";
4242

4343
public static void save(DynamicFPSConfig instance) {
44-
String data = GSON.toJson(instance) + "\n";
44+
JsonObject config = (JsonObject) GSON.toJsonTree(instance);
45+
JsonObject parent = (JsonObject) GSON.toJsonTree(DynamicFPSConfig.DEFAULT);
46+
47+
String data = GSON.toJson(removeUnchangedFields(config, parent)) + "\n";
4548

4649
Path cache = Platform.getInstance().getCacheDir();
47-
Path config = Platform.getInstance().getConfigDir().resolve(CONFIG_FILE);
50+
Path configs = Platform.getInstance().getConfigDir().resolve(CONFIG_FILE);
4851

4952
try {
5053
Path temp = Files.createTempFile(cache, "config", ".json");
5154

5255
Files.write(temp, data.getBytes(StandardCharsets.UTF_8));
53-
Serialization.move(temp, config); // Attempt atomic move, fall back otherwise.
56+
Serialization.move(temp, configs); // Attempt atomic move, fall back otherwise
5457
} catch (IOException e) {
5558
// Cloth Config's built-in saving does not support catching exceptions :(
5659
throw new RuntimeException("Failed to save or modify Dynamic FPS config!", e);
@@ -65,6 +68,25 @@ private static void move(Path from, Path to) throws IOException {
6568
}
6669
}
6770

71+
private static JsonObject removeUnchangedFields(JsonObject config, JsonObject parent) {
72+
// Recursively delete all fields that are equal to the defaults ...
73+
74+
parent.entrySet().forEach(entry -> {
75+
String name = entry.getKey();
76+
77+
JsonElement other = entry.getValue();
78+
JsonElement value = config.get(name);
79+
80+
if (value.equals(other)) {
81+
config.remove(name);
82+
} else if (value.isJsonObject() && other.isJsonObject()) {
83+
removeUnchangedFields((JsonObject) value, (JsonObject) other);
84+
}
85+
});
86+
87+
return config;
88+
}
89+
6890
@SuppressWarnings("deprecation")
6991
public static DynamicFPSConfig load() {
7092
byte[] data;
@@ -91,20 +113,45 @@ public static DynamicFPSConfig load() {
91113
return GSON.fromJson(root, DynamicFPSConfig.class); // Ignores regular constructor!
92114
}
93115

94-
private static void upgradeConfig(JsonObject root) {
95-
addIdleTime(root);
96-
upgradeVolumeMultiplier(root);
97-
addAbandonedConfig(root);
98-
addUncapMenuFrameRate(root);
99-
addEnabled(root);
100-
addVolumeTransitionSpeed(root);
101-
}
116+
public static DynamicFPSConfig loadDefault() {
117+
byte[] data;
118+
119+
try (InputStream stream = Serialization.class.getResourceAsStream("/assets/dynamic_fps/data/default_config.json")) {
120+
if (stream == null) {
121+
throw new IOException("Stream is null.");
122+
}
102123

103-
private static void addIdleTime(JsonObject root) {
104-
// Add idle_time field if it's missing
105-
if (!root.has("idle_time")) {
106-
root.addProperty("idle_time", 0);
124+
data = stream.readAllBytes();
125+
} catch (IOException e) {
126+
throw new RuntimeException("Failed to load Dynamic FPS config.", e);
107127
}
128+
129+
return GSON.fromJson(new String(data, StandardCharsets.UTF_8), DynamicFPSConfig.class);
130+
}
131+
132+
private static void upgradeConfig(JsonObject config) {
133+
// v3.3.0
134+
upgradeVolumeMultiplier(config);
135+
136+
// version agnostic
137+
addMissingFields(config, (JsonObject) GSON.toJsonTree(DynamicFPSConfig.DEFAULT));
138+
}
139+
140+
private static void addMissingFields(JsonObject config, JsonObject parent) {
141+
// Recursively add all fields that are missing from the user config
142+
143+
parent.entrySet().forEach(entry -> {
144+
String name = entry.getKey();
145+
146+
JsonElement other = entry.getValue();
147+
JsonElement value = config.get(name);
148+
149+
if (value == null) {
150+
config.add(name, other);
151+
} else if (value.isJsonObject() && other.isJsonObject()) {
152+
addMissingFields((JsonObject) value, (JsonObject) other);
153+
}
154+
});
108155
}
109156

110157
private static void upgradeVolumeMultiplier(JsonObject root) {
@@ -143,47 +190,6 @@ private static void upgradeVolumeMultiplier(JsonObject root) {
143190
}
144191
}
145192

146-
private static void addAbandonedConfig(JsonObject root) {
147-
// Add default config for abandoned power state
148-
JsonObject states = getStatesAsObject(root);
149-
150-
if (states == null) {
151-
return;
152-
}
153-
154-
if (states.has("abandoned")) {
155-
return;
156-
}
157-
158-
states.add("abandoned", GSON.toJsonTree(Config.getDefault(PowerState.ABANDONED)));
159-
}
160-
161-
private static void addUncapMenuFrameRate(JsonObject root) {
162-
// Add uncap_menu_frame_rate field if it's missing
163-
if (!root.has("uncap_menu_frame_rate")) {
164-
root.addProperty("uncap_menu_frame_rate", false);
165-
}
166-
}
167-
168-
private static void addEnabled(JsonObject root) {
169-
// Add enabled field if it's missing
170-
if (!root.has("enabled")) {
171-
root.addProperty("enabled", true);
172-
}
173-
}
174-
175-
private static void addVolumeTransitionSpeed(JsonObject root) {
176-
// Add volume_transition_speed object if it's missing
177-
if (!root.has("volume_transition_speed")) {
178-
JsonObject object = new JsonObject();
179-
180-
object.addProperty("up", 1.0f);
181-
object.addProperty("down", 0.5f);
182-
183-
root.add("volume_transition_speed", object);
184-
}
185-
}
186-
187193
private static @Nullable JsonObject getStatesAsObject(JsonObject root) {
188194
if (!root.has("states")) {
189195
return null;

0 commit comments

Comments
 (0)