/* * Decompiled with CFR 0.152. * * Could not load the following classes: * org.jspecify.annotations.Nullable */ package net.minecraft.util.thread; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.BiFunction; import net.minecraft.util.Mth; import net.minecraft.util.Util; import org.jspecify.annotations.Nullable; public class ParallelMapTransform { private static final int DEFAULT_TASKS_PER_THREAD = 16; public static CompletableFuture> schedule(Map input, BiFunction operation, int maxTaskCount, Executor executor) { int inputSize = input.size(); if (inputSize == 0) { return CompletableFuture.completedFuture(Map.of()); } if (inputSize == 1) { Map.Entry element = input.entrySet().iterator().next(); Object key = element.getKey(); Object value = element.getValue(); return CompletableFuture.supplyAsync(() -> { Object result = operation.apply(key, value); return result != null ? Map.of(key, result) : Map.of(); }, executor); } SplitterBase splitter = inputSize <= maxTaskCount ? new SingleTaskSplitter(operation, inputSize) : new BatchedTaskSplitter(operation, inputSize, maxTaskCount); return splitter.scheduleTasks(input, executor); } public static CompletableFuture> schedule(Map input, BiFunction operation, Executor executor) { int maxTaskCount = Util.maxAllowedExecutorThreads() * 16; return ParallelMapTransform.schedule(input, operation, maxTaskCount, executor); } private static class SingleTaskSplitter extends SplitterBase { private SingleTaskSplitter(BiFunction operation, int size) { super(operation, size, size); } @Override protected int batchSize(int index) { return 1; } @Override protected CompletableFuture scheduleBatch(Container container, int startIndex, int endIndex, Executor executor) { assert (startIndex + 1 == endIndex); return CompletableFuture.runAsync(() -> container.applyOperation(startIndex), executor); } @Override protected CompletableFuture> scheduleFinalOperation(CompletableFuture allTasksDone, Container container) { return allTasksDone.thenApply(ignored -> { HashMap result = new HashMap(container.size()); for (int i = 0; i < container.size(); ++i) { container.copyOut(i, result); } return result; }); } } private static class BatchedTaskSplitter extends SplitterBase { private final Map result; private final int batchSize; private final int firstUndersizedBatchIndex; private BatchedTaskSplitter(BiFunction operation, int size, int maxTasks) { super(operation, size, maxTasks); this.result = new HashMap(size); this.batchSize = Mth.positiveCeilDiv(size, maxTasks); int fullCapacity = this.batchSize * maxTasks; int leftoverCapacity = fullCapacity - size; this.firstUndersizedBatchIndex = maxTasks - leftoverCapacity; assert (this.firstUndersizedBatchIndex > 0 && this.firstUndersizedBatchIndex <= maxTasks); } @Override protected CompletableFuture scheduleBatch(Container container, int startIndex, int endIndex, Executor executor) { int batchSize = endIndex - startIndex; assert (batchSize == this.batchSize || batchSize == this.batchSize - 1); return CompletableFuture.runAsync(BatchedTaskSplitter.createTask(this.result, startIndex, endIndex, container), executor); } @Override protected int batchSize(int index) { return index < this.firstUndersizedBatchIndex ? this.batchSize : this.batchSize - 1; } private static Runnable createTask(Map result, int startIndex, int endIndex, Container container) { return () -> { for (int i = startIndex; i < endIndex; ++i) { container.applyOperation(i); } Map map = result; synchronized (map) { for (int i = startIndex; i < endIndex; ++i) { container.copyOut(i, result); } } }; } @Override protected CompletableFuture> scheduleFinalOperation(CompletableFuture allTasksDone, Container container) { Map result = this.result; return allTasksDone.thenApply(ignored -> result); } } private static abstract class SplitterBase { private int lastScheduledIndex; private int currentIndex; private final CompletableFuture[] tasks; private int batchIndex; private final Container container; private SplitterBase(BiFunction operation, int size, int taskCount) { this.container = new Container(operation, size); this.tasks = new CompletableFuture[taskCount]; } private int pendingBatchSize() { return this.currentIndex - this.lastScheduledIndex; } public CompletableFuture> scheduleTasks(Map input, Executor executor) { input.forEach((key, inputValue) -> { this.container.put(this.currentIndex++, key, inputValue); if (this.pendingBatchSize() == this.batchSize(this.batchIndex)) { this.tasks[this.batchIndex++] = this.scheduleBatch(this.container, this.lastScheduledIndex, this.currentIndex, executor); this.lastScheduledIndex = this.currentIndex; } }); assert (this.currentIndex == this.container.size()); assert (this.lastScheduledIndex == this.currentIndex); assert (this.batchIndex == this.tasks.length); return this.scheduleFinalOperation(CompletableFuture.allOf(this.tasks), this.container); } protected abstract int batchSize(int var1); protected abstract CompletableFuture scheduleBatch(Container var1, int var2, int var3, Executor var4); protected abstract CompletableFuture> scheduleFinalOperation(CompletableFuture var1, Container var2); } private record Container(BiFunction operation, @Nullable Object[] keys, @Nullable Object[] values) { public Container(BiFunction operation, int size) { this(operation, new Object[size], new Object[size]); } public void put(int index, K key, U input) { this.keys[index] = key; this.values[index] = input; } private @Nullable K key(int index) { return (K)this.keys[index]; } private @Nullable V output(int index) { return (V)this.values[index]; } private @Nullable U input(int index) { return (U)this.values[index]; } public void applyOperation(int index) { this.values[index] = this.operation.apply(this.key(index), this.input(index)); } public void copyOut(int index, Map output) { V value = this.output(index); if (value != null) { K key = this.key(index); output.put(key, value); } } public int size() { return this.keys.length; } } }