Parallelize construction of DeferredRegister registry objects
This commit is contained in:
parent
c6323fd62e
commit
5c0d23b2f0
|
|
@ -30,6 +30,7 @@ public class ModernFixEarlyConfig {
|
|||
this.addMixinRule("perf.async_jei", true);
|
||||
this.addMixinRule("perf.thread_priorities", true);
|
||||
this.addMixinRule("perf.preload_block_classes", true);
|
||||
this.addMixinRule("perf.parallel_deferred_suppliers", true);
|
||||
|
||||
/* Mod compat */
|
||||
if(FMLLoader.getLoadingModList().getModFileById("smoothboot") != null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package org.embeddedt.modernfix.mixin.perf.parallel_deferred_suppliers;
|
||||
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
import org.embeddedt.modernfix.registry.DeferredRegisterBaker;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(DeferredRegister.class)
|
||||
public class DeferredRegisterMixin {
|
||||
@Shadow(remap = false) private IForgeRegistry type;
|
||||
|
||||
@Shadow(remap = false) @Final private String modid;
|
||||
|
||||
@ModifyArg(method = "register(Ljava/lang/String;Ljava/util/function/Supplier;)Lnet/minecraftforge/fml/RegistryObject;", at = @At(value = "INVOKE", target = "Ljava/util/Map;putIfAbsent(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"), index = 1, remap = false)
|
||||
private Object swapForCachedSupplier(Object original) {
|
||||
if(this.type != null)
|
||||
return DeferredRegisterBaker.cacheForComputationLater(this.type.getRegistryName(), this.modid, (Supplier<?>)original);
|
||||
else
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package org.embeddedt.modernfix.mixin.perf.parallel_deferred_suppliers;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.fml.ModLoadingStage;
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import org.embeddedt.modernfix.registry.DeferredRegisterBaker;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Mixin(GameData.class)
|
||||
public class GameDataMixin {
|
||||
@Inject(method = "generateRegistryEvents", at = @At(value = "INVOKE", target = "Ljava/util/List;stream()Ljava/util/stream/Stream;"), locals = LocalCapture.CAPTURE_FAILHARD, remap = false)
|
||||
private static void bakeDeferredBeforeSendingEvents(CallbackInfoReturnable<Stream<ModLoadingStage.EventGenerator<?>>> cir, List<ResourceLocation> keys) {
|
||||
/* TODO also bake items, maybe? */
|
||||
DeferredRegisterBaker.bakeSuppliers(new ResourceLocation("minecraft", "block"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package org.embeddedt.modernfix.registry;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.ModWorkManager;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.util.CachedSupplier;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DeferredRegisterBaker {
|
||||
private static final HashMap<ResourceLocation, HashMap<String, List<CachedSupplier<?>>>> supplierMap = new HashMap<>();
|
||||
public static <T> Supplier<T> cacheForComputationLater(ResourceLocation registry, String modid, Supplier<T> supplier) {
|
||||
synchronized (supplierMap) {
|
||||
HashMap<String, List<CachedSupplier<?>>> registrySupplierMap = supplierMap.computeIfAbsent(registry, reg -> new HashMap<>());
|
||||
List<CachedSupplier<?>> modSupplierList = registrySupplierMap.computeIfAbsent(modid, id -> new ArrayList<>());
|
||||
CachedSupplier<T> cacher = new CachedSupplier<>(supplier);
|
||||
modSupplierList.add(cacher);
|
||||
return cacher;
|
||||
}
|
||||
}
|
||||
|
||||
public static void bakeSuppliers(ResourceLocation registry) {
|
||||
synchronized (supplierMap) {
|
||||
HashMap<String, List<CachedSupplier<?>>> registrySupplierMap = supplierMap.get(registry);
|
||||
if(registrySupplierMap == null)
|
||||
return;
|
||||
HashSet<String> finishedMods = new HashSet<>();
|
||||
finishedMods.add("minecraft");
|
||||
finishedMods.add("forge");
|
||||
HashMap<String, CompletableFuture<?>> submittedFutures = new HashMap<>();
|
||||
int numMods = ModList.get().getMods().size();
|
||||
Semaphore jobWaitingSemaphore = new Semaphore(0);
|
||||
ArrayList<ModInfo> remainingModList = new ArrayList<>(ModList.get().getMods());
|
||||
Stopwatch realtimeStopwatch = Stopwatch.createStarted();
|
||||
AtomicLong cpuLong = new AtomicLong(0);
|
||||
while(finishedMods.size() < numMods) {
|
||||
remainingModList.removeIf(modInfo -> {
|
||||
if(finishedMods.contains(modInfo.getModId()))
|
||||
return true;
|
||||
boolean allDependenciesLoaded = true;
|
||||
for(IModInfo.ModVersion dep : modInfo.getDependencies()) {
|
||||
if(dep.isMandatory() && !finishedMods.contains(dep.getModId())) {
|
||||
allDependenciesLoaded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!allDependenciesLoaded)
|
||||
return false;
|
||||
/* Submit job */
|
||||
List<CachedSupplier<?>> suppliersToCompute = registrySupplierMap.get(modInfo.getModId());
|
||||
if (suppliersToCompute == null || suppliersToCompute.size() == 0) {
|
||||
finishedMods.add(modInfo.getModId());
|
||||
return true;
|
||||
}
|
||||
Optional<? extends ModContainer> modContainerOpt = ModList.get().getModContainerById(modInfo.getModId());
|
||||
if(!modContainerOpt.isPresent())
|
||||
throw new IllegalStateException("Can't find mod container");
|
||||
ModContainer container = modContainerOpt.get();
|
||||
submittedFutures.put(modInfo.getModId(), CompletableFuture.runAsync(() -> {
|
||||
Supplier<?> contextExtension = ObfuscationReflectionHelper.getPrivateValue(ModContainer.class, container, "contextExtension");
|
||||
ModLoadingContext.get().setActiveContainer(container, contextExtension.get());
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
for (CachedSupplier<?> supplier : suppliersToCompute) {
|
||||
supplier.compute();
|
||||
}
|
||||
stopwatch.stop();
|
||||
cpuLong.addAndGet(stopwatch.elapsed(TimeUnit.MILLISECONDS));
|
||||
jobWaitingSemaphore.release();
|
||||
}, ModWorkManager.parallelExecutor()));
|
||||
return true;
|
||||
});
|
||||
try {
|
||||
jobWaitingSemaphore.acquire();
|
||||
} catch(InterruptedException e) {
|
||||
throw new RuntimeException("Unexpected interruption", e);
|
||||
}
|
||||
submittedFutures.entrySet().removeIf(entry -> {
|
||||
if(entry.getValue().isDone()) {
|
||||
finishedMods.add(entry.getKey());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
realtimeStopwatch.stop();
|
||||
ModernFix.LOGGER.info("CPU time spent constructing " + registry + " suppliers: " + cpuLong.get()/1000f + " seconds");
|
||||
ModernFix.LOGGER.info("Real time spent constructing " + registry + " suppliers: " + realtimeStopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package org.embeddedt.modernfix.util;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An implementation of Supplier that allows separating the time at which the value is computed from when it is
|
||||
* retrieved.
|
||||
*/
|
||||
public class CachedSupplier<T> implements Supplier<T> {
|
||||
private T value = null;
|
||||
|
||||
private boolean hasBeenComputed;
|
||||
private final Supplier<T> delegate;
|
||||
|
||||
public CachedSupplier(Supplier<T> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public synchronized void compute() {
|
||||
this.value = this.delegate.get();
|
||||
this.hasBeenComputed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T get() {
|
||||
if(this.hasBeenComputed) {
|
||||
this.hasBeenComputed = false;
|
||||
return this.value;
|
||||
} else {
|
||||
return this.delegate.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,9 @@
|
|||
"perf.reduce_blockstate_cache_rebuilds.BlockCallbacksMixin",
|
||||
"perf.boost_worker_count.UtilMixin",
|
||||
"perf.thread_priorities.UtilMixin",
|
||||
"perf.preload_block_classes.GameDataMixin"
|
||||
"perf.preload_block_classes.GameDataMixin",
|
||||
"perf.parallel_deferred_suppliers.DeferredRegisterMixin",
|
||||
"perf.parallel_deferred_suppliers.GameDataMixin"
|
||||
],
|
||||
"client": [
|
||||
"perf.skip_first_datapack_reload.MinecraftMixin",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user