Skip to content

Commit

Permalink
implement dynamic value constraints in control elements, cleanup cont…
Browse files Browse the repository at this point in the history
…rol elements
  • Loading branch information
douira committed Nov 3, 2024
1 parent 8e701ca commit fe70c25
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ public record Range(int min, int max, int step) {
public boolean isValueValid(int value) {
return value >= this.min && value <= this.max && (value - this.min) % this.step == 0;
}

public int getSpread() {
return this.max - this.min;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.caffeinemc.mods.sodium.api.config.ConfigState;
import net.caffeinemc.mods.sodium.api.config.StorageEventHandler;
import net.caffeinemc.mods.sodium.api.config.option.OptionFlag;
import net.caffeinemc.mods.sodium.client.config.value.DynamicValue;
import net.caffeinemc.mods.sodium.client.console.Console;
import net.caffeinemc.mods.sodium.client.console.message.MessageLevel;
import net.minecraft.client.Minecraft;
Expand All @@ -29,6 +30,9 @@ public Config(ImmutableList<ModOptions> modOptions) {
this.validateDependencies();

// load options initially from their bindings
for (var option : this.options.values()) {
option.loadValueInitial();
}
resetAllOptions();
}

Expand Down Expand Up @@ -93,6 +97,18 @@ private void validateDependencies() {
throw new IllegalArgumentException("Option " + option.id + " depends on non-existent option " + dependency);
}
}

// link dependents
option.visitDependentValues(dependent -> {
if (dependent instanceof DynamicValue<?> dynamicValue) {
for (var dependency : dependent.getDependencies()) {
var dependencyOption = this.options.get(dependency);
if (dependencyOption instanceof StatefulOption<?> statefulOption) {
statefulOption.registerDependent(dynamicValue);
}
}
}
});
}

// make sure there are no cycles
Expand All @@ -103,6 +119,11 @@ private void validateDependencies() {
}
}

void invalidateDependents(Collection<DynamicValue<?>> dependents) {
for (var dependent : dependents) {
dependent.invalidateCache();
}
}

private void checkDependencyCycles(Option option, ObjectOpenHashSet<ResourceLocation> stack, ObjectOpenHashSet<ResourceLocation> finished) {
if (!stack.add(option.id)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
import java.util.Collection;
import java.util.EnumSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

public class EnumOption<E extends Enum<E>> extends StatefulOption<E> {
final Class<E> enumClass;
public final Class<E> enumClass;

private final DependentValue<Set<E>> allowedValues;
private final Function<E, Component> elementNameProvider;
Expand All @@ -28,14 +29,27 @@ public class EnumOption<E extends Enum<E>> extends StatefulOption<E> {
this.elementNameProvider = elementNameProvider;
}

@Override
void visitDependentValues(Consumer<DependentValue<?>> visitor) {
super.visitDependentValues(visitor);
visitor.accept(this.allowedValues);
}

@Override
public boolean isValueValid(E value) {
return this.allowedValues.get(this.state).contains(value);
}

@Override
Control createControl() {
// TODO: doesn't update allowed values when dependencies change
return new CyclingControl<>(this, this.enumClass, this.elementNameProvider, this.allowedValues.get(this.state).toArray(this.enumClass.getEnumConstants()));
return new CyclingControl<>(this, this.enumClass);
}

public boolean isValueAllowed(E value) {
return this.allowedValues.get(this.state).contains(value);
}

public Component getElementName(E element) {
return this.elementNameProvider.apply(element);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import java.util.Collection;
import java.util.EnumSet;
import java.util.function.Consumer;
import java.util.function.Function;

public class IntegerOption extends StatefulOption<Integer> {
Expand All @@ -22,6 +23,12 @@ public class IntegerOption extends StatefulOption<Integer> {
this.valueFormatter = valueFormatter;
}

@Override
void visitDependentValues(Consumer<DependentValue<?>> visitor) {
super.visitDependentValues(visitor);
visitor.accept(this.range);
}

@Override
public boolean isValueValid(Integer value) {
return this.range.get(this.state).isValueValid(value);
Expand All @@ -30,11 +37,15 @@ public boolean isValueValid(Integer value) {
@Override
Control createControl() {
var range = this.range.get(this.state);
return new SliderControl(this, range.min(), range.max(), range.step(), this.valueFormatter);
return new SliderControl(this, range.min(), range.max(), range.step());
}

public Range getRange() {
return this.range.get(this.state);
}

public Component formatValue(int value) {
return this.valueFormatter.format(value);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import java.util.Collection;
import java.util.EnumSet;
import java.util.function.Consumer;

public abstract class Option {
final ResourceLocation id;
Expand Down Expand Up @@ -45,6 +46,14 @@ void setParentConfig(Config state) {
this.state = state;
}

void visitDependentValues(Consumer<DependentValue<?>> visitor) {
visitor.accept(this.enabled);
}

void loadValueInitial() {
// no-op
}

void resetFromBinding() {
// no-op
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package net.caffeinemc.mods.sodium.client.config.structure;

import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.caffeinemc.mods.sodium.api.config.StorageEventHandler;
import net.caffeinemc.mods.sodium.api.config.option.OptionBinding;
import net.caffeinemc.mods.sodium.api.config.option.OptionFlag;
import net.caffeinemc.mods.sodium.api.config.option.OptionImpact;
import net.caffeinemc.mods.sodium.client.config.value.DependentValue;
import net.caffeinemc.mods.sodium.client.config.value.DynamicValue;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;

import java.util.Collection;
import java.util.EnumSet;
import java.util.function.Consumer;
import java.util.function.Function;

public abstract class StatefulOption<V> extends Option {
Expand All @@ -20,6 +23,8 @@ public abstract class StatefulOption<V> extends Option {
final DependentValue<V> defaultValue;
final OptionBinding<V> binding;

private final Collection<DynamicValue<?>> dependents = new ObjectOpenHashSet<>(0);

private V value;
private V modifiedValue;

Expand All @@ -33,12 +38,32 @@ public abstract class StatefulOption<V> extends Option {
this.binding = binding;
}

@Override
void visitDependentValues(Consumer<DependentValue<?>> visitor) {
super.visitDependentValues(visitor);
visitor.accept(this.defaultValue);
}

void registerDependent(DynamicValue<?> dependent) {
this.dependents.add(dependent);
}

public void modifyValue(V value) {
this.modifiedValue = value;
if (this.modifiedValue != value) {
this.modifiedValue = value;
this.state.invalidateDependents(this.dependents);
}
}

@Override
void loadValueInitial() {
this.value = this.binding.load();
this.modifiedValue = this.value;
}

@Override
void resetFromBinding() {
var previousValue = this.modifiedValue;
this.value = this.binding.load();

if (!isValueValid(this.value)) {
Expand All @@ -51,11 +76,18 @@ void resetFromBinding() {
}

this.modifiedValue = this.value;
if (this.value != previousValue) {
this.state.invalidateDependents(this.dependents);
}
}

public V getValidatedValue() {
if (!isValueValid(this.modifiedValue)) {
var previousValue = this.modifiedValue;
this.modifiedValue = this.defaultValue.get(this.state);
if (this.modifiedValue != previousValue) {
this.state.invalidateDependents(this.dependents);
}
}

return this.modifiedValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class DynamicValue<V> implements DependentValue<V>, ConfigState {
private final Set<ResourceLocation> dependencies;
private final Function<ConfigState, V> provider;
private Config state;
private V valueCache;

public DynamicValue(Function<ConfigState, V> provider, ResourceLocation[] dependencies) {
this.provider = provider;
Expand All @@ -20,17 +21,25 @@ public DynamicValue(Function<ConfigState, V> provider, ResourceLocation[] depend

@Override
public V get(Config state) {
if (this.valueCache != null) {
return this.valueCache;
}

this.state = state;
var result = this.provider.apply(this);
this.valueCache = this.provider.apply(this);
this.state = null;
return result;
return this.valueCache;
}

@Override
public Collection<ResourceLocation> getDependencies() {
return this.dependencies;
}

public void invalidateCache() {
this.valueCache = null;
}

private void validateRead(ResourceLocation id) {
if (!this.dependencies.contains(id)) {
throw new IllegalStateException("Attempted to read option value that is not a declared dependency");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import com.mojang.blaze3d.platform.Monitor;
import com.mojang.blaze3d.platform.VideoMode;
import com.mojang.blaze3d.platform.Window;
import net.caffeinemc.mods.sodium.api.config.*;
import net.caffeinemc.mods.sodium.api.config.structure.*;
import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint;
import net.caffeinemc.mods.sodium.api.config.StorageEventHandler;
import net.caffeinemc.mods.sodium.api.config.option.OptionBinding;
import net.caffeinemc.mods.sodium.api.config.option.OptionFlag;
import net.caffeinemc.mods.sodium.api.config.option.OptionImpact;
import net.caffeinemc.mods.sodium.api.config.option.Range;
import net.caffeinemc.mods.sodium.api.config.structure.*;
import net.caffeinemc.mods.sodium.client.SodiumClientMod;
import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils;
import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds;
Expand All @@ -26,6 +28,8 @@
import org.lwjgl.opengl.GLCapabilities;

import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Optional;

// TODO: get initialValue from the vanilla options (it's private)
Expand Down Expand Up @@ -158,13 +162,45 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
builder.createIntegerOption(ResourceLocation.parse("foo:baz"))
.setStorageHandler(() -> {
})
.setName(Component.literal("Hello"))
.setTooltip(Component.literal("Bla"))
.setName(Component.literal("Baz"))
.setTooltip(Component.literal("Baz"))
.setValueFormatter(ControlValueFormatterImpls.number())
.setDefaultValue(5)
.setRange(0, 10, 1)
.setBinding(new LocalBinding<>(5))
.setEnabledProvider((state) -> state.readBooleanOption(ResourceLocation.parse("foo:bar")), ResourceLocation.parse("foo:bar"))
.setEnabledProvider(state -> state.readBooleanOption(ResourceLocation.parse("foo:bar")), ResourceLocation.parse("foo:bar"))
)
.addOption(
builder.createIntegerOption(ResourceLocation.parse("foo:bla"))
.setStorageHandler(() -> {
})
.setName(Component.literal("Bla"))
.setTooltip(Component.literal("hello"))
.setValueFormatter(ControlValueFormatterImpls.number())
.setDefaultValue(5)
.setRangeProvider(
state -> new Range(state.readBooleanOption(ResourceLocation.parse("foo:bar")) ? 0 : 1, 5, 1),
ResourceLocation.parse("foo:bar"))
.setBinding(new LocalBinding<>(5))
)
.addOption(
builder.createEnumOption(ResourceLocation.parse("foo:zot"), OptionImpact.class)
.setStorageHandler(() -> {
})
.setName(Component.literal("Zot"))
.setTooltip(Component.literal("hello"))
.setDefaultValue(OptionImpact.LOW)
.setAllowedValuesProvider(
state -> {
var set = EnumSet.noneOf(OptionImpact.class);
var value = state.readIntOption(ResourceLocation.parse("foo:bla"));
var list = Arrays.asList(OptionImpact.values());
set.addAll(list.subList(0, Math.min(value + 1, list.size())));
return set;
},
ResourceLocation.parse("foo:bla"))
.setElementNameProvider(value -> Component.literal(value.name()))
.setBinding(new LocalBinding<>(OptionImpact.LOW))
)
.addOption(
builder.createExternalButtonOption(ResourceLocation.parse("foo:button"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@
// TODO: show option group's names somewhere
// TODO: add button or some other way for user to reset a specific option, all options on a page, and all options of a mod to their default values (not just "reset" changes, but reset to default value)
// TODO: make RD option respect Vanilla's >16 RD only allowed if memory >1GB constraint
// TODO: use update tag in stateful options to prevent multiple calls to dependencies
// TODO: make sure value constraints are actually dynamic (the controls receive them statically)
public class VideoSettingsScreen extends Screen implements ScreenPromptable {
private final List<ControlElement> controls = new ArrayList<>();

Expand Down
Loading

0 comments on commit fe70c25

Please sign in to comment.