mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-12 16:27:47 +00:00
Make LazyOptional's internal references to invalidation listeners use WeakReference, and allow modders to unregister themselves. Closes #8805
This commit is contained in:
1 file changed
+38
-53
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.common.util;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -40,15 +41,14 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
public class LazyOptional<T>
|
||||
{
|
||||
public class LazyOptional<T> {
|
||||
private final NonNullSupplier<T> supplier;
|
||||
private final Object lock = new Object();
|
||||
// null -> not resolved yet
|
||||
// non-null and contains non-null value -> resolved
|
||||
// non-null and contains null -> resolved, but supplier returned null (contract violation)
|
||||
private Mutable<T> resolved;
|
||||
private Set<NonNullConsumer<LazyOptional<T>>> listeners = new HashSet<>();
|
||||
private Set<WeakReference<NonNullConsumer<LazyOptional<T>>>> listeners = new HashSet<>();
|
||||
private boolean isValid = true;
|
||||
|
||||
private static final @NotNull LazyOptional<Void> EMPTY = new LazyOptional<>(null);
|
||||
@@ -62,16 +62,14 @@ public class LazyOptional<T>
|
||||
* null, but can be null itself. If null, this method
|
||||
* returns {@link #empty()}.
|
||||
*/
|
||||
public static <T> LazyOptional<T> of(final @Nullable NonNullSupplier<T> instanceSupplier)
|
||||
{
|
||||
public static <T> LazyOptional<T> of(final @Nullable NonNullSupplier<T> instanceSupplier) {
|
||||
return instanceSupplier == null ? empty() : new LazyOptional<>(instanceSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The singleton empty instance
|
||||
*/
|
||||
public static <T> LazyOptional<T> empty()
|
||||
{
|
||||
public static <T> LazyOptional<T> empty() {
|
||||
return EMPTY.cast();
|
||||
}
|
||||
|
||||
@@ -83,27 +81,21 @@ public class LazyOptional<T>
|
||||
* @return This {@link LazyOptional}, cast to the inferred generic type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <X> LazyOptional<X> cast()
|
||||
{
|
||||
public <X> LazyOptional<X> cast() {
|
||||
return (LazyOptional<X>)this;
|
||||
}
|
||||
|
||||
private LazyOptional(@Nullable NonNullSupplier<T> instanceSupplier)
|
||||
{
|
||||
private LazyOptional(@Nullable NonNullSupplier<T> instanceSupplier) {
|
||||
this.supplier = instanceSupplier;
|
||||
}
|
||||
|
||||
private @Nullable T getValue()
|
||||
{
|
||||
private @Nullable T getValue() {
|
||||
if (!isValid || supplier == null)
|
||||
return null;
|
||||
if (resolved == null)
|
||||
{
|
||||
synchronized (lock)
|
||||
{
|
||||
if (resolved == null) {
|
||||
synchronized (lock) {
|
||||
// resolved == null: Double checked locking to prevent two threads from resolving
|
||||
if (resolved == null)
|
||||
{
|
||||
if (resolved == null) {
|
||||
T temp = supplier.get();
|
||||
if (temp == null)
|
||||
LOGGER.catching(Level.WARN, new NullPointerException("Supplier should not return null value"));
|
||||
@@ -114,13 +106,10 @@ public class LazyOptional<T>
|
||||
return resolved.getValue();
|
||||
}
|
||||
|
||||
private T getValueUnsafe()
|
||||
{
|
||||
private T getValueUnsafe() {
|
||||
T ret = getValue();
|
||||
if (ret == null)
|
||||
{
|
||||
throw new IllegalStateException("LazyOptional is empty or otherwise returned null from getValue() unexpectedly");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -130,8 +119,7 @@ public class LazyOptional<T>
|
||||
* @return {@code true} if this {@link LazyOptional} is non-empty, i.e. holds a
|
||||
* non-null supplier
|
||||
*/
|
||||
public boolean isPresent()
|
||||
{
|
||||
public boolean isPresent() {
|
||||
return supplier != null && isValid;
|
||||
}
|
||||
|
||||
@@ -142,8 +130,7 @@ public class LazyOptional<T>
|
||||
* @param consumer The {@link NonNullConsumer} to run if this optional is non-empty.
|
||||
* @throws NullPointerException if {@code consumer} is null and this {@link LazyOptional} is non-empty
|
||||
*/
|
||||
public void ifPresent(NonNullConsumer<? super T> consumer)
|
||||
{
|
||||
public void ifPresent(NonNullConsumer<? super T> consumer) {
|
||||
Objects.requireNonNull(consumer);
|
||||
T val = getValue();
|
||||
if (isValid && val != null)
|
||||
@@ -169,8 +156,7 @@ public class LazyOptional<T>
|
||||
* present, otherwise an empty {@link LazyOptional}
|
||||
* @throws NullPointerException if {@code mapper} is null.
|
||||
*/
|
||||
public <U> LazyOptional<U> lazyMap(NonNullFunction<? super T, ? extends U> mapper)
|
||||
{
|
||||
public <U> LazyOptional<U> lazyMap(NonNullFunction<? super T, ? extends U> mapper) {
|
||||
Objects.requireNonNull(mapper);
|
||||
return isPresent() ? of(() -> mapper.apply(getValueUnsafe())) : empty();
|
||||
}
|
||||
@@ -189,8 +175,7 @@ public class LazyOptional<T>
|
||||
* present, otherwise an empty {@link Optional}
|
||||
* @throws NullPointerException if {@code mapper} is null.
|
||||
*/
|
||||
public <U> Optional<U> map(NonNullFunction<? super T, ? extends U> mapper)
|
||||
{
|
||||
public <U> Optional<U> map(NonNullFunction<? super T, ? extends U> mapper) {
|
||||
Objects.requireNonNull(mapper);
|
||||
return isPresent() ? Optional.of(mapper.apply(getValueUnsafe())) : Optional.empty();
|
||||
}
|
||||
@@ -211,8 +196,7 @@ public class LazyOptional<T>
|
||||
* @throws NullPointerException If {@code predicate} is null and this
|
||||
* {@link Optional} is non-empty
|
||||
*/
|
||||
public Optional<T> filter(NonNullPredicate<? super T> predicate)
|
||||
{
|
||||
public Optional<T> filter(NonNullPredicate<? super T> predicate) {
|
||||
Objects.requireNonNull(predicate);
|
||||
final T value = getValue(); // To keep the non-null contract we have to evaluate right now. Should we allow this function at all?
|
||||
return value != null && predicate.test(value) ? Optional.of(value) : Optional.empty();
|
||||
@@ -222,8 +206,7 @@ public class LazyOptional<T>
|
||||
* Resolves the value of this LazyOptional, turning it into a standard non-lazy {@link Optional<T>}
|
||||
* @return The resolved optional.
|
||||
*/
|
||||
public Optional<T> resolve()
|
||||
{
|
||||
public Optional<T> resolve() {
|
||||
return isPresent() ? Optional.of(getValueUnsafe()) : Optional.empty();
|
||||
}
|
||||
|
||||
@@ -234,8 +217,7 @@ public class LazyOptional<T>
|
||||
* @param other the value to be returned if this {@link LazyOptional} is empty
|
||||
* @return the result of the supplier, if non-empty, otherwise {@code other}
|
||||
*/
|
||||
public T orElse(T other)
|
||||
{
|
||||
public T orElse(T other) {
|
||||
T val = getValue();
|
||||
return val != null ? val : other;
|
||||
}
|
||||
@@ -251,8 +233,7 @@ public class LazyOptional<T>
|
||||
* @throws NullPointerException If {@code other} is null and this
|
||||
* {@link LazyOptional} is non-empty
|
||||
*/
|
||||
public T orElseGet(NonNullSupplier<? extends T> other)
|
||||
{
|
||||
public T orElseGet(NonNullSupplier<? extends T> other) {
|
||||
T val = getValue();
|
||||
return val != null ? val : other.get();
|
||||
}
|
||||
@@ -273,8 +254,7 @@ public class LazyOptional<T>
|
||||
* @throws NullPointerException If {@code exceptionSupplier} is null and this
|
||||
* {@link LazyOptional} is empty
|
||||
*/
|
||||
public <X extends Throwable> T orElseThrow(NonNullSupplier<? extends X> exceptionSupplier) throws X
|
||||
{
|
||||
public <X extends Throwable> T orElseThrow(NonNullSupplier<? extends X> exceptionSupplier) throws X {
|
||||
T val = getValue();
|
||||
if (val != null)
|
||||
return val;
|
||||
@@ -286,16 +266,19 @@ public class LazyOptional<T>
|
||||
* <p>
|
||||
* If this {@link LazyOptional} is empty, the listener will be called immediately.
|
||||
*/
|
||||
public void addListener(NonNullConsumer<LazyOptional<T>> listener)
|
||||
{
|
||||
public void addListener(NonNullConsumer<LazyOptional<T>> listener) {
|
||||
if (isPresent())
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
this.listeners.add(new WeakReference<>(listener));
|
||||
else
|
||||
{
|
||||
listener.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a {@link NonNullConsumer listener} from the list to be notified when this {@link LazyOptional} becomes invalid (via {@link #invalidate()}).<br>
|
||||
* This allows modder who know they will not need to be notified, to remove the hard reference that this holds to their listener.
|
||||
*/
|
||||
public void removeListener(NonNullConsumer<LazyOptional<T>> listener) {
|
||||
this.listeners.removeIf(e -> e.get() == null || e.get() == listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,12 +295,14 @@ public class LazyOptional<T>
|
||||
* etc... This allows modders to keep a cache of capability objects instead of
|
||||
* re-checking them every tick.
|
||||
*/
|
||||
public void invalidate()
|
||||
{
|
||||
if (this.isValid)
|
||||
{
|
||||
public void invalidate() {
|
||||
if (this.isValid) {
|
||||
this.isValid = false;
|
||||
this.listeners.forEach(e -> e.accept(this));
|
||||
this.listeners.stream()
|
||||
.map(WeakReference::get)
|
||||
.filter(e -> e != null)
|
||||
.forEach(e -> e.accept(this));
|
||||
this.listeners.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user