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 DataFetcher.Task createTask(String id, Callable 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(Either value, long time) { } @Environment(EnvType.CLIENT) class SubscribedTask { private final DataFetcher.Task task; private final Consumer output; private long lastCheckTime = -1L; SubscribedTask(final DataFetcher.Task task, final Consumer output) { this.task = task; this.output = output; } void update(long time) { this.task.updateIfNeeded(time); this.runCallbackIfNeeded(); } void runCallbackIfNeeded() { DataFetcher.SuccessfulComputationResult successfulComputationResult = this.task.lastResult; if (successfulComputationResult != null && this.lastCheckTime < successfulComputationResult.time) { this.output.accept(successfulComputationResult.value); this.lastCheckTime = successfulComputationResult.time; } } void runCallback() { DataFetcher.SuccessfulComputationResult 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> subscriptions = new ArrayList(); public void subscribe(DataFetcher.Task task, Consumer output) { DataFetcher.SubscribedTask 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 value, long time) { } @Environment(EnvType.CLIENT) public class Task { private final String id; private final Callable updater; private final long period; private final RepeatedDelayStrategy repeatStrategy; @Nullable private CompletableFuture> pendingTask; @Nullable DataFetcher.SuccessfulComputationResult lastResult; private long nextUpdate = -1L; Task(final String id, final Callable 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 computationResult = (DataFetcher.ComputationResult)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; } } }