178 lines
5.6 KiB
Java
178 lines
5.6 KiB
Java
package com.mojang.realmsclient.gui.task;
|
|
|
|
import com.mojang.datafixers.util.Either;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.time.Duration;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.concurrent.Callable;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.function.Consumer;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.util.TimeSource;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class DataFetcher {
|
|
static final Logger LOGGER = LogUtils.getLogger();
|
|
final Executor executor;
|
|
final TimeUnit resolution;
|
|
final TimeSource timeSource;
|
|
|
|
public DataFetcher(Executor executor, TimeUnit resolution, TimeSource timeSource) {
|
|
this.executor = executor;
|
|
this.resolution = resolution;
|
|
this.timeSource = timeSource;
|
|
}
|
|
|
|
public <T> DataFetcher.Task<T> createTask(String id, Callable<T> updater, Duration period, RepeatedDelayStrategy repeatStrategy) {
|
|
long l = this.resolution.convert(period);
|
|
if (l == 0L) {
|
|
throw new IllegalArgumentException("Period of " + period + " too short for selected resolution of " + this.resolution);
|
|
} else {
|
|
return new DataFetcher.Task<>(id, updater, l, repeatStrategy);
|
|
}
|
|
}
|
|
|
|
public DataFetcher.Subscription createSubscription() {
|
|
return new DataFetcher.Subscription();
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
record ComputationResult<T>(Either<T, Exception> value, long time) {
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
class SubscribedTask<T> {
|
|
private final DataFetcher.Task<T> task;
|
|
private final Consumer<T> output;
|
|
private long lastCheckTime = -1L;
|
|
|
|
SubscribedTask(final DataFetcher.Task<T> task, final Consumer<T> output) {
|
|
this.task = task;
|
|
this.output = output;
|
|
}
|
|
|
|
void update(long time) {
|
|
this.task.updateIfNeeded(time);
|
|
this.runCallbackIfNeeded();
|
|
}
|
|
|
|
void runCallbackIfNeeded() {
|
|
DataFetcher.SuccessfulComputationResult<T> successfulComputationResult = this.task.lastResult;
|
|
if (successfulComputationResult != null && this.lastCheckTime < successfulComputationResult.time) {
|
|
this.output.accept(successfulComputationResult.value);
|
|
this.lastCheckTime = successfulComputationResult.time;
|
|
}
|
|
}
|
|
|
|
void runCallback() {
|
|
DataFetcher.SuccessfulComputationResult<T> successfulComputationResult = this.task.lastResult;
|
|
if (successfulComputationResult != null) {
|
|
this.output.accept(successfulComputationResult.value);
|
|
this.lastCheckTime = successfulComputationResult.time;
|
|
}
|
|
}
|
|
|
|
void reset() {
|
|
this.task.reset();
|
|
this.lastCheckTime = -1L;
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class Subscription {
|
|
private final List<DataFetcher.SubscribedTask<?>> subscriptions = new ArrayList();
|
|
|
|
public <T> void subscribe(DataFetcher.Task<T> task, Consumer<T> output) {
|
|
DataFetcher.SubscribedTask<T> subscribedTask = DataFetcher.this.new SubscribedTask<>(task, output);
|
|
this.subscriptions.add(subscribedTask);
|
|
subscribedTask.runCallbackIfNeeded();
|
|
}
|
|
|
|
public void forceUpdate() {
|
|
for (DataFetcher.SubscribedTask<?> subscribedTask : this.subscriptions) {
|
|
subscribedTask.runCallback();
|
|
}
|
|
}
|
|
|
|
public void tick() {
|
|
for (DataFetcher.SubscribedTask<?> subscribedTask : this.subscriptions) {
|
|
subscribedTask.update(DataFetcher.this.timeSource.get(DataFetcher.this.resolution));
|
|
}
|
|
}
|
|
|
|
public void reset() {
|
|
for (DataFetcher.SubscribedTask<?> subscribedTask : this.subscriptions) {
|
|
subscribedTask.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
record SuccessfulComputationResult<T>(T value, long time) {
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class Task<T> {
|
|
private final String id;
|
|
private final Callable<T> updater;
|
|
private final long period;
|
|
private final RepeatedDelayStrategy repeatStrategy;
|
|
@Nullable
|
|
private CompletableFuture<DataFetcher.ComputationResult<T>> pendingTask;
|
|
@Nullable
|
|
DataFetcher.SuccessfulComputationResult<T> lastResult;
|
|
private long nextUpdate = -1L;
|
|
|
|
Task(final String id, final Callable<T> updater, final long period, final RepeatedDelayStrategy repeatStrategy) {
|
|
this.id = id;
|
|
this.updater = updater;
|
|
this.period = period;
|
|
this.repeatStrategy = repeatStrategy;
|
|
}
|
|
|
|
void updateIfNeeded(long time) {
|
|
if (this.pendingTask != null) {
|
|
DataFetcher.ComputationResult<T> computationResult = (DataFetcher.ComputationResult<T>)this.pendingTask.getNow(null);
|
|
if (computationResult == null) {
|
|
return;
|
|
}
|
|
|
|
this.pendingTask = null;
|
|
long l = computationResult.time;
|
|
computationResult.value().ifLeft(object -> {
|
|
this.lastResult = new DataFetcher.SuccessfulComputationResult<>((T)object, l);
|
|
this.nextUpdate = l + this.period * this.repeatStrategy.delayCyclesAfterSuccess();
|
|
}).ifRight(exception -> {
|
|
long m = this.repeatStrategy.delayCyclesAfterFailure();
|
|
DataFetcher.LOGGER.warn("Failed to process task {}, will repeat after {} cycles", this.id, m, exception);
|
|
this.nextUpdate = l + this.period * m;
|
|
});
|
|
}
|
|
|
|
if (this.nextUpdate <= time) {
|
|
this.pendingTask = CompletableFuture.supplyAsync(() -> {
|
|
try {
|
|
T object = (T)this.updater.call();
|
|
long lx = DataFetcher.this.timeSource.get(DataFetcher.this.resolution);
|
|
return new DataFetcher.ComputationResult<>(Either.left(object), lx);
|
|
} catch (Exception var4x) {
|
|
long lx = DataFetcher.this.timeSource.get(DataFetcher.this.resolution);
|
|
return new DataFetcher.ComputationResult(Either.right(var4x), lx);
|
|
}
|
|
}, DataFetcher.this.executor);
|
|
}
|
|
}
|
|
|
|
public void reset() {
|
|
this.pendingTask = null;
|
|
this.lastResult = null;
|
|
this.nextUpdate = -1L;
|
|
}
|
|
}
|
|
}
|