rewrite MultiStepBinding.asyncMap

This commit is contained in:
yushijinhun 2019-02-23 21:58:44 +08:00
parent 46d27a04a2
commit bc9a9ffb72
No known key found for this signature in database
GPG Key ID: 5BC167F73EA558E4
3 changed files with 80 additions and 42 deletions

View File

@ -35,6 +35,7 @@ import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
@ -163,20 +164,23 @@ public final class TexturesLoader {
return Optional.empty(); return Optional.empty();
} }
}) })
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))) .flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))
.filter(it -> StringUtils.isNotBlank(it.getUrl())))
.asyncMap(it -> { .asyncMap(it -> {
if (it.isPresent()) { if (it.isPresent()) {
Texture texture = it.get(); Texture texture = it.get();
try { return CompletableFuture.supplyAsync(() -> {
return loadTexture(texture); try {
} catch (IOException e) { return loadTexture(texture);
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e); } catch (IOException e) {
return getDefaultSkin(TextureModel.detectModelName(texture.getMetadata())); LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
} return uuidFallback;
}
}, POOL);
} else { } else {
return uuidFallback; return CompletableFuture.completedFuture(uuidFallback);
} }
}, uuidFallback, POOL); }, uuidFallback);
} }
// ==== // ====

View File

@ -220,4 +220,8 @@ public final class Lang {
consumer.accept(t); consumer.accept(t);
return t; return t;
} }
public static void handleUncaughtException(Throwable e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
} }

View File

@ -18,13 +18,13 @@
package org.jackhuang.hmcl.util.javafx; package org.jackhuang.hmcl.util.javafx;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.util.Lang.handleUncaughtException;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.CompletableFuture;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jackhuang.hmcl.util.InvocationDispatcher;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.ObjectBinding;
@ -58,8 +58,8 @@ public abstract class MultiStepBinding<T, U> extends ObjectBinding<U> {
return new FlatMappedBinding<>(map(mapper), nullAlternative); return new FlatMappedBinding<>(map(mapper), nullAlternative);
} }
public <V> MultiStepBinding<?, V> asyncMap(Function<U, V> mapper, V initial, Executor executor) { public <V> MultiStepBinding<?, V> asyncMap(Function<U, CompletableFuture<V>> mapper, V initial) {
return new AsyncMappedBinding<>(this, mapper, executor, initial); return new AsyncMappedBinding<>(this, mapper, initial);
} }
private static class SimpleBinding<T> extends MultiStepBinding<T, T> { private static class SimpleBinding<T> extends MultiStepBinding<T, T> {
@ -79,8 +79,8 @@ public abstract class MultiStepBinding<T, U> extends ObjectBinding<U> {
} }
@Override @Override
public <V> MultiStepBinding<?, V> asyncMap(Function<T, V> mapper, V initial, Executor executor) { public <V> MultiStepBinding<?, V> asyncMap(Function<T, CompletableFuture<V>> mapper, V initial) {
return new AsyncMappedBinding<>(predecessor, mapper, executor, initial); return new AsyncMappedBinding<>(predecessor, mapper, initial);
} }
} }
@ -136,49 +136,79 @@ public abstract class MultiStepBinding<T, U> extends ObjectBinding<U> {
private static class AsyncMappedBinding<T, U> extends MultiStepBinding<T, U> { private static class AsyncMappedBinding<T, U> extends MultiStepBinding<T, U> {
private final InvocationDispatcher<T> dispatcher;
private boolean initialized = false; private boolean initialized = false;
private T prev; private T prev;
private U value; private U value;
public AsyncMappedBinding(ObservableValue<T> predecessor, Function<T, U> mapper, Executor executor, U initial) { private final Function<T, CompletableFuture<U>> mapper;
private T computingPrev;
private boolean computing = false;
public AsyncMappedBinding(ObservableValue<T> predecessor, Function<T, CompletableFuture<U>> mapper, U initial) {
super(predecessor); super(predecessor);
this.value = initial; this.value = initial;
this.mapper = mapper;
}
dispatcher = InvocationDispatcher.runOn(executor, arg -> { private void tryUpdateValue(T currentPrev) {
synchronized (this) { synchronized (this) {
if (initialized && Objects.equals(arg, prev)) { if ((initialized && Objects.equals(prev, currentPrev))
return; || isComputing(currentPrev)) {
} return;
} }
U newValue = mapper.apply(arg); computing = true;
synchronized (this) { computingPrev = currentPrev;
prev = arg; }
value = newValue;
initialized = true; CompletableFuture<U> task;
try {
task = requireNonNull(mapper.apply(currentPrev));
} catch (Throwable e) {
valueUpdateFailed(currentPrev);
throw e;
}
task.handle((result, e) -> {
if (e == null) {
valueUpdate(currentPrev, result);
Platform.runLater(this::invalidate);
} else {
handleUncaughtException(e);
valueUpdateFailed(currentPrev);
} }
Platform.runLater(this::invalidate); return null;
}); });
} }
// called on FX thread, this method is serial private void valueUpdate(T currentPrev, U computed) {
@Override
protected U computeValue() {
T currentPrev = predecessor.getValue();
U value;
boolean updateNeeded = false;
synchronized (this) { synchronized (this) {
value = this.value; if (isComputing(currentPrev)) {
if (!initialized || !Objects.equals(currentPrev, prev)) { computing = false;
updateNeeded = true; computingPrev = null;
prev = currentPrev;
value = computed;
initialized = true;
} }
} }
if (updateNeeded) {
dispatcher.accept(currentPrev);
}
return value;
} }
private void valueUpdateFailed(T currentPrev) {
synchronized (this) {
if (isComputing(currentPrev)) {
computing = false;
computingPrev = null;
}
}
}
private boolean isComputing(T prev) {
return computing && Objects.equals(prev, computingPrev);
}
@Override
protected U computeValue() {
tryUpdateValue(predecessor.getValue());
return value;
}
} }
} }