diff options
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/impl/OptionImpl.java')
-rw-r--r-- | src/main/java/dev/isxander/yacl3/impl/OptionImpl.java | 162 |
1 files changed, 111 insertions, 51 deletions
diff --git a/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java index afe9517..296c01f 100644 --- a/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java +++ b/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java @@ -9,6 +9,7 @@ import net.minecraft.network.chat.Component; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiConsumer; @@ -17,40 +18,39 @@ import java.util.function.Function; import java.util.function.Supplier; @ApiStatus.Internal -public final class OptionImpl<T> implements Option<T> { +public class OptionImpl<T> implements Option<T> { private final Component name; private OptionDescription description; private final Controller<T> controller; - private final Binding<T> binding; private boolean available; private final ImmutableSet<OptionFlag> flags; - private T pendingValue; - - private final List<BiConsumer<Option<T>, T>> listeners; - private int listenerTriggerDepth = 0; + private final StateManager<T> stateManager; + private final List<OptionEventListener<T>> listeners; + private int currentListenerDepth; public OptionImpl( @NotNull Component name, @NotNull Function<T, OptionDescription> descriptionFunction, @NotNull Function<Option<T>, Controller<T>> controlGetter, - @NotNull Binding<T> binding, + @NotNull StateManager<T> stateManager, boolean available, ImmutableSet<OptionFlag> flags, - @NotNull Collection<BiConsumer<Option<T>, T>> listeners + @NotNull Collection<OptionEventListener<T>> listeners ) { this.name = name; - this.binding = new SafeBinding<>(binding); this.available = available; this.flags = flags; this.listeners = new ArrayList<>(listeners); - this.pendingValue = binding.getValue(); + this.stateManager = stateManager; this.controller = controlGetter.apply(this); - addListener((opt, pending) -> description = descriptionFunction.apply(pending)); - triggerListeners(true); + this.stateManager.addListener((oldValue, newValue) -> + triggerListener(OptionEventListener.Event.STATE_CHANGE, false)); + addEventListener((opt, event) -> description = descriptionFunction.apply(opt.pendingValue())); + triggerListener(OptionEventListener.Event.INITIAL, false); } @Override @@ -74,8 +74,17 @@ public final class OptionImpl<T> implements Option<T> { } @Override + public @NotNull StateManager<T> stateManager() { + return stateManager; + } + + @Override + @Deprecated public @NotNull Binding<T> binding() { - return binding; + if (stateManager instanceof ProvidesBindingForDeprecation) { + return ((ProvidesBindingForDeprecation<T>) stateManager).getBinding(); + } + throw new UnsupportedOperationException("Binding is not available for this option - using a new state manager which does not directly expose the binding as it may not have one."); } @Override @@ -91,9 +100,9 @@ public final class OptionImpl<T> implements Option<T> { if (changed) { if (!available) { - this.pendingValue = binding().getValue(); + this.stateManager.sync(); } - this.triggerListeners(!available); + this.triggerListener(OptionEventListener.Event.AVAILABILITY_CHANGE, !available); } } @@ -104,26 +113,25 @@ public final class OptionImpl<T> implements Option<T> { @Override public boolean changed() { - return !binding().getValue().equals(pendingValue); + return !this.stateManager.isSynced(); } @Override public @NotNull T pendingValue() { - return pendingValue; + return this.stateManager.get(); } @Override public void requestSet(@NotNull T value) { Validate.notNull(value, "`value` cannot be null"); - pendingValue = value; - this.triggerListeners(true); + this.stateManager.set(value); } @Override public boolean applyValue() { if (changed()) { - binding().setValue(pendingValue); + this.stateManager.apply(); return true; } return false; @@ -131,41 +139,44 @@ public final class OptionImpl<T> implements Option<T> { @Override public void forgetPendingValue() { - requestSet(binding().getValue()); + this.stateManager.sync(); } @Override public void requestSetDefault() { - requestSet(binding().defaultValue()); + this.stateManager.resetToDefault(StateManager.ResetAction.BY_OPTION); } @Override public boolean isPendingValueDefault() { - return binding().defaultValue().equals(pendingValue()); + return this.stateManager.isDefault(); } @Override + public void addEventListener(OptionEventListener<T> listener) { + this.listeners.add(listener); + } + + @Override + @Deprecated public void addListener(BiConsumer<Option<T>, T> changedListener) { - this.listeners.add(changedListener); + addEventListener((opt, event) -> changedListener.accept(opt, opt.pendingValue())); } - private void triggerListeners(boolean bypass) { - if (bypass || listenerTriggerDepth == 0) { - if (listenerTriggerDepth > 10) { - throw new IllegalStateException("Listener trigger depth exceeded 10! This means a listener triggered a listener etc etc 10 times deep. This is likely a bug in the mod using YACL!"); - } + private void triggerListener(OptionEventListener.Event event, boolean allowDepth) { + if (allowDepth || currentListenerDepth == 0) { + Validate.isTrue( + currentListenerDepth <= 10, + "Listener depth exceeded 10! Possible cyclic listener pattern: a listener triggered an event that triggered the initial event etc etc." + ); - this.listenerTriggerDepth++; + currentListenerDepth++; - for (BiConsumer<Option<T>, T> listener : listeners) { - try { - listener.accept(this, pendingValue); - } catch (Exception e) { - YACLConstants.LOGGER.error("Exception whilst triggering listener for option '%s'".formatted(name.getString()), e); - } + for (OptionEventListener<T> listener : listeners) { + listener.onEvent(this, event); } - this.listenerTriggerDepth--; + currentListenerDepth--; } } @@ -177,15 +188,17 @@ public final class OptionImpl<T> implements Option<T> { private Function<Option<T>, Controller<T>> controlGetter; - private Binding<T> binding; private boolean available = true; - private boolean instant = false; - private final Set<OptionFlag> flags = new HashSet<>(); - private final List<BiConsumer<Option<T>, T>> listeners = new ArrayList<>(); + private final List<OptionEventListener<T>> listeners = new ArrayList<>(); + + private @Nullable Binding<T> binding; + private boolean instantDeprecated = false; + + private @Nullable StateManager<T> stateManager; @Override public Builder<T> name(@NotNull Component name) { @@ -222,8 +235,20 @@ public final class OptionImpl<T> implements Option<T> { } @Override + public Builder<T> stateManager(@NotNull StateManager<T> stateManager) { + Validate.notNull(stateManager, "`stateManager` cannot be null"); + + Validate.isTrue(binding == null, "Cannot set state manager when binding is set"); + Validate.isTrue(!instantDeprecated, "Cannot set state manager when instant is set"); + + this.stateManager = stateManager; + return this; + } + + @Override public Builder<T> binding(@NotNull Binding<T> binding) { Validate.notNull(binding, "`binding` cannot be null"); + Validate.isTrue(stateManager == null, "Cannot set binding when state manager is set"); this.binding = binding; return this; @@ -235,8 +260,7 @@ public final class OptionImpl<T> implements Option<T> { Validate.notNull(getter, "`getter` must not be null"); Validate.notNull(setter, "`setter` must not be null"); - this.binding = Binding.generic(def, getter, setter); - return this; + return binding(Binding.generic(def, getter, setter)); } @Override @@ -262,34 +286,70 @@ public final class OptionImpl<T> implements Option<T> { } @Override + @Deprecated public Builder<T> instant(boolean instant) { - this.instant = instant; + Validate.isTrue(stateManager == null, "Cannot set instant when state manager is set"); + YACLConstants.LOGGER.error("Option.Builder#instant is deprecated behaviour. Please use a custom state manager instead: `.state(StateManager.createInstant(Binding))`"); + + this.instantDeprecated = instant; return this; } @Override - public Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener) { + public Builder<T> addListener(@NotNull OptionEventListener<T> listener) { + Validate.notNull(listener, "`listener` must not be null"); + this.listeners.add(listener); return this; } @Override + public Builder<T> addListeners(@NotNull Collection<@NotNull OptionEventListener<T>> optionEventListeners) { + Validate.notNull(optionEventListeners, "`optionEventListeners` must not be null"); + + this.listeners.addAll(optionEventListeners); + return this; + } + + @Override + public Builder<T> listener(@NotNull BiConsumer<Option<T>, T> listener) { + Validate.notNull(listener, "`listener` must not be null"); + + return this.addListener((opt, event) -> listener.accept(opt, opt.pendingValue())); + } + + @Override public Builder<T> listeners(@NotNull Collection<BiConsumer<Option<T>, T>> listeners) { - this.listeners.addAll(listeners); + Validate.notNull(listeners, "`listeners` must not be null"); + + this.addListeners(listeners.stream() + .map(listener -> + (OptionEventListener<T>) (opt, event) -> + listener.accept(opt, opt.pendingValue()) + ).toList() + ); return this; } @Override public Option<T> build() { Validate.notNull(controlGetter, "`control` must not be null when building `Option`"); - Validate.notNull(binding, "`binding` must not be null when building `Option`"); - Validate.isTrue(!instant || flags.isEmpty(), "instant application does not support option flags"); - if (instant) { - listeners.add((opt, pendingValue) -> opt.applyValue()); + if (instantDeprecated) { + if (binding == null) { + throw new IllegalStateException("Cannot build option with instant when binding is not set"); + } + Validate.isTrue(flags.isEmpty(), "instant application does not support option flags"); + + this.stateManager = StateManager.createInstant(binding); + } else if (binding != null) { + stateManager = StateManager.createSimple(binding); } + Validate.notNull(stateManager, "State manager must be set, either by using .binding() to create a simple manager or .state() to create an advanced one"); + + Validate.isTrue(!stateManager.isAlwaysSynced() || flags.isEmpty(), "Always synced state managers do not support option flags."); - return new OptionImpl<>(name, descriptionFunction, controlGetter, binding, available, ImmutableSet.copyOf(flags), listeners); + return new OptionImpl<>(name, descriptionFunction, controlGetter, stateManager, available, ImmutableSet.copyOf(flags), listeners); } } } |