From 3083aebe47f63661238ee2b521b0451af2d95e9f Mon Sep 17 00:00:00 2001 From: isXander Date: Sat, 12 Aug 2023 15:04:45 +0100 Subject: Fix `Option#setAvailable` not updating the reset button when used outside of a listener (@enjarai). Made listener system more robust. --- .../isxander/yacl3/impl/ListOptionEntryImpl.java | 2 +- .../dev/isxander/yacl3/impl/ListOptionImpl.java | 44 +++++++++++++++++++--- .../java/dev/isxander/yacl3/impl/OptionImpl.java | 29 +++++++++++++- 3 files changed, 67 insertions(+), 8 deletions(-) (limited to 'common/src/main/java') diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java index bdb8de8..72254cf 100644 --- a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionEntryImpl.java @@ -138,7 +138,7 @@ public final class ListOptionEntryImpl implements ListOptionEntry { @Override public void setValue(T newValue) { value = newValue; - group.callListeners(); + group.callListeners(true); } @Override diff --git a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java index 12842c1..f45e368 100644 --- a/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/impl/ListOptionImpl.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.impl.utils.YACLConstants; import net.minecraft.network.chat.Component; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.ApiStatus; @@ -30,8 +31,10 @@ public final class ListOptionImpl implements ListOption { private final boolean insertEntriesAtEnd; private final ImmutableSet flags; private final EntryFactory entryFactory; + private final List>, List>> listeners; private final List refreshListeners; + private int listenerTriggerDepth = 0; public ListOptionImpl(@NotNull Component name, @NotNull OptionDescription description, @NotNull Binding> binding, @NotNull Supplier initialValue, @NotNull Function, Controller> controllerFunction, ImmutableSet flags, boolean collapsed, boolean available, int minimumNumberOfEntries, int maximumNumberOfEntries, boolean insertEntriesAtEnd, Collection>, List>> listeners) { this.name = name; @@ -49,7 +52,7 @@ public final class ListOptionImpl implements ListOption { this.listeners = new ArrayList<>(); this.listeners.addAll(listeners); this.refreshListeners = new ArrayList<>(); - callListeners(); + callListeners(true); } @Override @@ -170,7 +173,12 @@ public final class ListOptionImpl implements ListOption { @Override public void setAvailable(boolean available) { + boolean changed = this.available != available; + this.available = available; + + if (changed) + callListeners(false); } @Override @@ -205,14 +213,30 @@ public final class ListOptionImpl implements ListOption { return values.stream().map(entryFactory::create).collect(Collectors.toList()); } - void callListeners() { + void callListeners(boolean bypass) { List pendingValue = pendingValue(); - this.listeners.forEach(listener -> listener.accept(this, pendingValue)); + 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!"); + } + + this.listenerTriggerDepth++; + + for (BiConsumer>, List> listener : listeners) { + try { + listener.accept(this, pendingValue); + } catch (Exception e) { + YACLConstants.LOGGER.error("Exception whilst triggering listener for option '%s'".formatted(name.getString()), e); + } + } + + this.listenerTriggerDepth--; + } } private void onRefresh() { refreshListeners.forEach(Runnable::run); - callListeners(); + callListeners(true); } private class EntryFactory { @@ -234,7 +258,7 @@ public final class ListOptionImpl implements ListOption { private Function, Controller> controllerFunction; private Binding> binding = null; private final Set flags = new HashSet<>(); - private T initialValue; + private Supplier initialValue; private boolean collapsed = false; private boolean available = true; private int minimumNumberOfEntries = 0; @@ -259,13 +283,21 @@ public final class ListOptionImpl implements ListOption { } @Override - public Builder initial(@NotNull T initialValue) { + public Builder initial(@NotNull Supplier initialValue) { Validate.notNull(initialValue, "`initialValue` cannot be empty"); this.initialValue = initialValue; return this; } + @Override + public Builder initial(@NotNull T initialValue) { + Validate.notNull(initialValue, "`initialValue` cannot be empty"); + + this.initialValue = () -> initialValue; + return this; + } + @Override public Builder controller(@NotNull Function, ControllerBuilder> controller) { Validate.notNull(controller, "`controller` cannot be null"); diff --git a/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java b/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java index 01a6287..165f38d 100644 --- a/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/impl/OptionImpl.java @@ -3,6 +3,7 @@ package dev.isxander.yacl3.impl; import com.google.common.collect.ImmutableSet; import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.impl.utils.YACLConstants; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import org.apache.commons.lang3.Validate; @@ -28,6 +29,7 @@ public final class OptionImpl implements Option { private T pendingValue; private final List, T>> listeners; + private int listenerTriggerDepth = 0; public OptionImpl( @NotNull Component name, @@ -82,7 +84,12 @@ public final class OptionImpl implements Option { @Override public void setAvailable(boolean available) { + boolean changed = this.available != available; + this.available = available; + + if (changed) + this.triggerListeners(false); } @Override @@ -103,7 +110,7 @@ public final class OptionImpl implements Option { @Override public void requestSet(T value) { pendingValue = value; - listeners.forEach(listener -> listener.accept(this, pendingValue)); + this.triggerListeners(true); } @Override @@ -135,6 +142,26 @@ public final class OptionImpl implements Option { this.listeners.add(changedListener); } + 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!"); + } + + this.listenerTriggerDepth++; + + for (BiConsumer, 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); + } + } + + this.listenerTriggerDepth--; + } + } + @ApiStatus.Internal public static class BuilderImpl implements Builder { private Component name = Component.literal("Name not specified!").withStyle(ChatFormatting.RED); -- cgit