Merge remote-tracking branch 'origin/1.20' into 1.21.1
This commit is contained in:
commit
13820f7bbf
|
|
@ -2,9 +2,34 @@ package org.embeddedt.modernfix.command;
|
|||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
|
||||
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
|
||||
public class ModernFixCommands {
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(literal("modernfix")
|
||||
.then(literal("mcfunctions").requires(source -> source.hasPermission(3))
|
||||
.executes(context -> {
|
||||
ServerLevel level = context.getSource().getLevel();
|
||||
if(level == null) {
|
||||
context.getSource().sendFailure(Component.literal("Couldn't find server level"));
|
||||
return 0;
|
||||
}
|
||||
if (level.getServer().getFunctions() instanceof IProfilingServerFunctionManager profiler) {
|
||||
context.getSource().sendSuccess(() -> Component.literal("mcfunction runtime breakdown:"), false);
|
||||
for(String line : profiler.mfix$getProfilingResults().split("\n")) {
|
||||
context.getSource().sendSuccess(() -> Component.literal(line), false);
|
||||
}
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
context.getSource().sendFailure(Component.literal("ModernFix mcfunction profiling is not enabled on this server."));
|
||||
return 0;
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package org.embeddedt.modernfix.common.mixin.feature.mcfunction_profiling;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.functions.CommandFunction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.ServerFunctionManager;
|
||||
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
|
||||
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.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ServerFunctionManager.class)
|
||||
public class ServerFunctionManagerMixin implements IProfilingServerFunctionManager {
|
||||
@Shadow @Final private static ResourceLocation TICK_FUNCTION_TAG;
|
||||
|
||||
private final Map<ResourceLocation, Stopwatch> mfix$functionWatches = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At("HEAD"))
|
||||
private void resetWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
mfix$functionWatches.values().forEach(Stopwatch::reset);
|
||||
}
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/functions/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)V"))
|
||||
private void startWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Local(ordinal = 0) CommandFunction<CommandSourceStack> function, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
|
||||
watchRef.set(null);
|
||||
if (identifier == TICK_FUNCTION_TAG) {
|
||||
var watch = mfix$functionWatches.computeIfAbsent(function.id(), i -> Stopwatch.createUnstarted());
|
||||
watch.start();
|
||||
watchRef.set(watch);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/functions/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)V", shift = At.Shift.AFTER))
|
||||
private void stopWatch(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci, @Share("stopwatch") LocalRef<Stopwatch> watchRef) {
|
||||
var watch = watchRef.get();
|
||||
if (watch != null && watch.isRunning()) {
|
||||
watch.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "executeTagFunctions", at = @At("RETURN"))
|
||||
private void pruneUnusedWatches(Collection<CommandFunction<CommandSourceStack>> functionObjects, ResourceLocation identifier, CallbackInfo ci) {
|
||||
mfix$functionWatches.values().removeIf(watch -> watch.elapsed().isZero());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mfix$getProfilingResults() {
|
||||
var list = new ArrayList<>(mfix$functionWatches.entrySet());
|
||||
list.sort(Comparator.<Map.Entry<ResourceLocation, Stopwatch>, Duration>comparing(e -> e.getValue().elapsed()).reversed());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (var entry : list) {
|
||||
sb.append(entry.getKey().toString());
|
||||
sb.append(" - ");
|
||||
sb.append(entry.getValue().toString());
|
||||
sb.append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,16 +8,38 @@ import org.spongepowered.asm.mixin.injection.At;
|
|||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(ProfiledReloadInstance.class)
|
||||
public class ProfiledReloadInstanceMixin {
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Decorate reload listeners with their class name as well as the simple name
|
||||
*/
|
||||
@ModifyVariable(method = "<init>", at = @At("HEAD"), argsOnly = true, ordinal = 0)
|
||||
private static List<PreparableReloadListener> getWrappedListeners(List<PreparableReloadListener> listeners) {
|
||||
List<PreparableReloadListener> newList = new ArrayList<>(listeners.size());
|
||||
for(PreparableReloadListener listener : listeners) {
|
||||
newList.add(new NamedPreparableResourceListener(listener));
|
||||
// No need to wrap listeners that are already wrapped/provided by a mod loader
|
||||
String className = listener.getClass().getName();
|
||||
if (className.startsWith("net.minecraftforge.") || className.startsWith("net.neoforged.") || className.startsWith("net.fabricmc.")) {
|
||||
newList.add(listener);
|
||||
} else {
|
||||
newList.add(new NamedPreparableResourceListener(listener));
|
||||
}
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Place most expensive reload listeners first
|
||||
*/
|
||||
@ModifyVariable(method = "finish", ordinal = 0, argsOnly = true, at = @At("HEAD"))
|
||||
private List<ProfiledReloadInstance.State> sortStates(List<ProfiledReloadInstance.State> datapoints) {
|
||||
datapoints = new ArrayList<>(datapoints);
|
||||
datapoints.sort(Comparator.<ProfiledReloadInstance.State>comparingLong(s -> s.preparationNanos.get() + s.reloadNanos.get()).reversed());
|
||||
return datapoints;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.resourcepacks;
|
||||
|
||||
import net.minecraft.server.packs.PackLocationInfo;
|
||||
import net.minecraft.server.packs.PackResources;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.server.packs.PathPackResources;
|
||||
import org.embeddedt.modernfix.resources.ICachingResourcePack;
|
||||
import org.embeddedt.modernfix.resources.PackResourcesCacheEngine;
|
||||
import org.embeddedt.modernfix.util.PackTypeHelper;
|
||||
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.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(value = PathPackResources.class, priority = 1100)
|
||||
public abstract class PathPackResourcesMixin implements ICachingResourcePack {
|
||||
|
||||
@Shadow @Final private Path root;
|
||||
private PackResourcesCacheEngine cacheEngine;
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void cacheResources(PackLocationInfo location, Path root, CallbackInfo ci) {
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
private PackResourcesCacheEngine generateResourceCache() {
|
||||
synchronized (this) {
|
||||
PackResourcesCacheEngine engine = this.cacheEngine;
|
||||
if(engine != null)
|
||||
return engine;
|
||||
this.cacheEngine = engine = new PackResourcesCacheEngine((type) -> this.root.resolve(type.getDirectory()));
|
||||
return engine;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCache() {
|
||||
this.cacheEngine = null;
|
||||
}
|
||||
|
||||
@Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true)
|
||||
private void useCacheForNamespaces(PackType type, CallbackInfoReturnable<Set<String>> cir) {
|
||||
PackResourcesCacheEngine engine = cacheEngine;
|
||||
if(engine != null) {
|
||||
Set<String> namespaces = engine.getNamespaces(type);
|
||||
if(namespaces != null)
|
||||
cir.setReturnValue(namespaces);
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "getRootResource", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;exists(Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z"))
|
||||
private boolean useCacheForExistence(Path path, LinkOption[] options, String[] originalPaths) {
|
||||
// the cache only stores things with a namespace and pack type
|
||||
if(originalPaths.length < 3 || (!Objects.equals(originalPaths[0], "assets") && !Objects.equals(originalPaths[0], "data")))
|
||||
return Files.exists(path, options);
|
||||
else
|
||||
return this.generateResourceCache().hasResource(originalPaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason Use cached listing of mod resources
|
||||
*/
|
||||
@Inject(method = "listResources", at = @At("HEAD"), cancellable = true)
|
||||
private void fastGetResources(PackType type, String namespace, String path, PackResources.ResourceOutput resourceOutput, CallbackInfo ci)
|
||||
{
|
||||
if(!PackTypeHelper.isVanillaPackType(type))
|
||||
return;
|
||||
ci.cancel();
|
||||
this.generateResourceCache().collectResources(type, namespace, path.split("/"), Integer.MAX_VALUE, resourceOutput);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package org.embeddedt.modernfix.common.mixin.perf.resourcepacks;
|
||||
|
||||
import net.minecraft.server.packs.resources.ReloadableResourceManager;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.resources.PackResourcesCacheEngine;
|
||||
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;
|
||||
|
||||
@Mixin(ReloadableResourceManager.class)
|
||||
public class ReloadableResourceManagerMixin {
|
||||
@Inject(method = "createReload", at = @At("HEAD"))
|
||||
private void invalidateResourceCaches(CallbackInfoReturnable<?> cir) {
|
||||
ModernFix.LOGGER.info("Invalidating pack caches");
|
||||
PackResourcesCacheEngine.invalidate();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package org.embeddedt.modernfix.duck;
|
||||
|
||||
public interface IProfilingServerFunctionManager {
|
||||
String mfix$getProfilingResults();
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@ package org.embeddedt.modernfix.entity;
|
|||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Maps;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderers;
|
||||
|
|
@ -12,14 +14,20 @@ import org.embeddedt.modernfix.ModernFix;
|
|||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@SuppressWarnings("OptionalAssignedToNull")
|
||||
public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>> {
|
||||
private final Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders;
|
||||
private final LoadingCache<EntityType<?>, EntityRenderer<?>> rendererMap;
|
||||
private final LoadingCache<EntityType<?>, Optional<EntityRenderer<?>>> rendererMap;
|
||||
private final EntityRendererProvider.Context context;
|
||||
|
||||
public EntityRendererMap(Map<EntityType<?>, EntityRendererProvider<?>> rendererProviders, EntityRendererProvider.Context context) {
|
||||
|
|
@ -28,22 +36,22 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
this.rendererMap = CacheBuilder.newBuilder().build(new RenderConstructor());
|
||||
}
|
||||
|
||||
class RenderConstructor extends CacheLoader<EntityType<?>, EntityRenderer<?>> {
|
||||
class RenderConstructor extends CacheLoader<EntityType<?>, Optional<EntityRenderer<?>>> {
|
||||
@Override
|
||||
public EntityRenderer<?> load(EntityType<?> key) throws Exception {
|
||||
public Optional<EntityRenderer<?>> load(EntityType<?> key) throws Exception {
|
||||
EntityRendererProvider<?> provider = rendererProviders.get(key);
|
||||
if(provider == null)
|
||||
return Optional.empty();
|
||||
synchronized(EntityRenderers.class) {
|
||||
EntityRenderer<?> renderer;
|
||||
try {
|
||||
if(provider == null)
|
||||
throw new RuntimeException("Provider not registered");
|
||||
renderer = provider.create(context);
|
||||
ModernFix.LOGGER.info("Loaded entity {}", BuiltInRegistries.ENTITY_TYPE.getKey(key));
|
||||
} catch(RuntimeException e) {
|
||||
ModernFix.LOGGER.error("Failed to create entity model for " + BuiltInRegistries.ENTITY_TYPE.getKey(key) + ":", e);
|
||||
renderer = new ErroredEntityRenderer<>(context);
|
||||
}
|
||||
return renderer;
|
||||
return Optional.ofNullable(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,10 +79,8 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
@Override
|
||||
public EntityRenderer<?> get(Object o) {
|
||||
try {
|
||||
EntityRenderer<?> renderer = rendererMap.get((EntityType<?>)o);
|
||||
if(renderer == null)
|
||||
throw new AssertionError("Returned entity renderer should never be null");
|
||||
return renderer;
|
||||
Optional<EntityRenderer<?>> renderer = rendererMap.get((EntityType<?>)o);
|
||||
return renderer.orElse(null);
|
||||
} catch (IllegalStateException e) {
|
||||
return null; /* emulate value not being present if recursive load occurs */
|
||||
} catch (ExecutionException e) {
|
||||
|
|
@ -85,21 +91,21 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
@Nullable
|
||||
@Override
|
||||
public EntityRenderer<?> put(EntityType<?> entityType, EntityRenderer<?> entityRenderer) {
|
||||
EntityRenderer<?> old = rendererMap.getIfPresent(entityType);
|
||||
rendererMap.put(entityType, entityRenderer);
|
||||
return old;
|
||||
Optional<EntityRenderer<?>> old = rendererMap.getIfPresent(entityType);
|
||||
rendererMap.put(entityType, Optional.ofNullable(entityRenderer));
|
||||
return old != null ? old.orElse(null) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderer<?> remove(Object o) {
|
||||
EntityRenderer<?> r = rendererMap.getIfPresent(o);
|
||||
Optional<EntityRenderer<?>> old = rendererMap.getIfPresent(o);
|
||||
rendererMap.invalidate(o);
|
||||
return r;
|
||||
return old != null ? old.orElse(null) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends EntityType<?>, ? extends EntityRenderer<?>> map) {
|
||||
rendererMap.putAll(map);
|
||||
rendererMap.putAll(Maps.transformValues(map, Optional::ofNullable));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -116,12 +122,64 @@ public class EntityRendererMap implements Map<EntityType<?>, EntityRenderer<?>>
|
|||
@NotNull
|
||||
@Override
|
||||
public Collection<EntityRenderer<?>> values() {
|
||||
return rendererMap.asMap().values();
|
||||
return new AbstractCollection<>() {
|
||||
@Override
|
||||
public Iterator<EntityRenderer<?>> iterator() {
|
||||
return Iterators.transform(Iterators.unmodifiableIterator(rendererProviders.keySet().iterator()), EntityRendererMap.this::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return rendererProviders.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class Entry implements Map.Entry<EntityType<?>, EntityRenderer<?>> {
|
||||
private final EntityType<?> key;
|
||||
|
||||
private Entry(EntityType<?> key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType<?> getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderer<?> getValue() {
|
||||
return get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityRenderer<?> setValue(EntityRenderer<?> value) {
|
||||
return put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<Map.Entry<EntityType<?>, EntityRenderer<?>>> entrySet() {
|
||||
return rendererMap.asMap().entrySet();
|
||||
return new AbstractSet<>() {
|
||||
@Override
|
||||
public Iterator<Map.Entry<EntityType<?>, EntityRenderer<?>>> iterator() {
|
||||
return Iterators.transform(Iterators.unmodifiableIterator(rendererProviders.keySet().iterator()), Entry::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (o instanceof Map.Entry<?,?> e) {
|
||||
return rendererProviders.containsKey(e.getKey()) && Objects.equals(get(e.getKey()), e.getValue());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return rendererProviders.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,88 +3,131 @@ package org.embeddedt.modernfix.resources;
|
|||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.PackResources;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import org.embeddedt.modernfix.ModernFix;
|
||||
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
|
||||
import org.embeddedt.modernfix.util.PackTypeHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The core of the resource pack cache system.
|
||||
*
|
||||
* Using a dedicated set and also separate lists is important; testing without this showed a huge performance
|
||||
* drop.
|
||||
*/
|
||||
public class PackResourcesCacheEngine {
|
||||
private static final Joiner SLASH_JOINER = Joiner.on('/');
|
||||
|
||||
private final Map<PackType, Set<String>> namespacesByType;
|
||||
private final Set<CachedResourcePath> containedPaths;
|
||||
private final EnumMap<PackType, Map<String, List<CachedResourcePath>>> resourceListings;
|
||||
static class Node {
|
||||
Map<String, Node> children;
|
||||
|
||||
void optimize() {
|
||||
if (children != null) {
|
||||
for (var entry : children.entrySet()) {
|
||||
var oldNode = entry.getValue();
|
||||
oldNode.optimize();
|
||||
if (oldNode.children == null) {
|
||||
entry.setValue(EMPTY);
|
||||
}
|
||||
}
|
||||
children = Map.copyOf(children);
|
||||
} else {
|
||||
children = Map.of();
|
||||
}
|
||||
}
|
||||
|
||||
void collectResources(String namespace, Path baseNioPath, String[] pathComponents, int curIndex, int maxDepth, PackResources.ResourceOutput output) {
|
||||
if (curIndex > maxDepth) {
|
||||
return;
|
||||
}
|
||||
if (curIndex < pathComponents.length) {
|
||||
String component;
|
||||
do {
|
||||
component = pathComponents[curIndex];
|
||||
if (!component.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
curIndex++;
|
||||
maxDepth++;
|
||||
} while(true);
|
||||
|
||||
Node n = getChild(component);
|
||||
if (n != null) {
|
||||
n.collectResources(namespace, baseNioPath, pathComponents, curIndex + 1, maxDepth, output);
|
||||
}
|
||||
} else {
|
||||
// We reached the desired path. Collect all resources
|
||||
this.outputResources(namespace, baseNioPath, String.join("/", pathComponents), output);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void outputResources(String namespace, Path baseNioPath, String path, PackResources.ResourceOutput output) {
|
||||
if (children.isEmpty()) {
|
||||
// This is a terminal node.
|
||||
ResourceLocation location = ResourceLocation.fromNamespaceAndPath(namespace, path);
|
||||
output.accept(location, () -> Files.newInputStream(baseNioPath.resolve(path)));
|
||||
} else {
|
||||
for (var entry : children.entrySet()) {
|
||||
entry.getValue().outputResources(namespace, baseNioPath, path + "/" + entry.getKey(), output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Node getChild(String name) {
|
||||
return children.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Node EMPTY = new Node();
|
||||
|
||||
private final Node root = new Node();
|
||||
private final Map<PackType, Path> rootPathsByType = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
private volatile boolean cacheGenerationFlag = false;
|
||||
private List<Runnable> cacheGenerationTasks = new ArrayList<>();
|
||||
private Path debugPath;
|
||||
|
||||
public PackResourcesCacheEngine(Function<PackType, Set<String>> namespacesRetriever, BiFunction<PackType, String, Path> basePathRetriever) {
|
||||
this.namespacesByType = new EnumMap<>(PackType.class);
|
||||
for(PackType type : PackType.values()) {
|
||||
if(!PackTypeHelper.isVanillaPackType(type))
|
||||
continue;
|
||||
this.namespacesByType.put(type, namespacesRetriever.apply(type));
|
||||
}
|
||||
this.containedPaths = new ObjectOpenHashSet<>();
|
||||
this.resourceListings = new EnumMap<>(PackType.class);
|
||||
public PackResourcesCacheEngine(Function<PackType, Path> basePathRetriever) {
|
||||
// used for log message
|
||||
this.debugPath = basePathRetriever.apply(PackType.CLIENT_RESOURCES, "minecraft").toAbsolutePath();
|
||||
this.debugPath = basePathRetriever.apply(PackType.CLIENT_RESOURCES).toAbsolutePath();
|
||||
this.root.children = new Object2ObjectOpenHashMap<>();
|
||||
ObjectOpenHashSet<String> pathKeys = new ObjectOpenHashSet<>();
|
||||
for(PackType type : PackType.values()) {
|
||||
Collection<String> namespaces = PackTypeHelper.isVanillaPackType(type) ? this.namespacesByType.get(type) : namespacesRetriever.apply(type);
|
||||
Collection<Pair<String, Path>> namespacedRoots = namespaces.stream().map(s -> Pair.of(s, basePathRetriever.apply(type, s).toAbsolutePath())).collect(Collectors.toList());
|
||||
var typeRoot = new Node();
|
||||
this.root.children.put(type.getDirectory(), typeRoot);
|
||||
Path root = basePathRetriever.apply(type);
|
||||
this.rootPathsByType.put(type, root);
|
||||
cacheGenerationTasks.add(() -> {
|
||||
ImmutableMap.Builder<String, List<CachedResourcePath>> packTypedMap = ImmutableMap.builder();
|
||||
for(Pair<String, Path> pair : namespacedRoots) {
|
||||
try {
|
||||
ImmutableList.Builder<CachedResourcePath> namespacedList = ImmutableList.builder();
|
||||
String namespace = pair.getFirst();
|
||||
Path root = pair.getSecond();
|
||||
String[] prefix = new String[] { type.getDirectory(), namespace };
|
||||
try (Stream<Path> stream = Files.find(root, Integer.MAX_VALUE, (p, a) -> a.isRegularFile())) {
|
||||
stream
|
||||
.map(path -> root.relativize(path.toAbsolutePath()))
|
||||
.filter(PackResourcesCacheEngine::isValidCachedResourcePath)
|
||||
.forEach(path -> {
|
||||
CachedResourcePath cachedPath = new CachedResourcePath(prefix, path);
|
||||
synchronized (this.containedPaths) {
|
||||
this.containedPaths.add(cachedPath);
|
||||
try {
|
||||
try (Stream<Path> stream = Files.find(root, Integer.MAX_VALUE, (p, a) -> a.isRegularFile())) {
|
||||
stream
|
||||
.map(path -> root.relativize(path.toAbsolutePath()))
|
||||
.filter(PackResourcesCacheEngine::isValidCachedResourcePath)
|
||||
.forEach(path -> {
|
||||
var node = typeRoot;
|
||||
for (Path component : path) {
|
||||
String key = pathKeys.addOrGet(component.toString());
|
||||
if (node.children == null) {
|
||||
node.children = new Object2ObjectOpenHashMap<>();
|
||||
}
|
||||
//if(!cachedPath.getFileName().endsWith(".mcmeta"))
|
||||
namespacedList.add(cachedPath);
|
||||
});
|
||||
}
|
||||
packTypedMap.put(namespace, namespacedList.build());
|
||||
} catch(IOException ignored) {
|
||||
node = node.children.computeIfAbsent(key, $ -> new Node());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
synchronized (this.resourceListings) {
|
||||
this.resourceListings.put(type, packTypedMap.build());
|
||||
} catch(IOException ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
cacheGenerationTasks.add(() -> {
|
||||
((ObjectOpenHashSet<CachedResourcePath>)this.containedPaths).trim();
|
||||
});
|
||||
cacheGenerationTasks.add(this.root::optimize);
|
||||
}
|
||||
|
||||
private static boolean isValidCachedResourcePath(Path path) {
|
||||
|
|
@ -103,8 +146,9 @@ public class PackResourcesCacheEngine {
|
|||
}
|
||||
|
||||
public Set<String> getNamespaces(PackType type) {
|
||||
awaitLoad();
|
||||
if(PackTypeHelper.isVanillaPackType(type))
|
||||
return this.namespacesByType.get(type);
|
||||
return this.root.getChild(type.getDirectory()).children.keySet();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
|
@ -131,56 +175,34 @@ public class PackResourcesCacheEngine {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean hasResource(String path) {
|
||||
awaitLoad();
|
||||
return this.containedPaths.contains(new CachedResourcePath(path));
|
||||
}
|
||||
|
||||
public boolean hasResource(String[] paths) {
|
||||
awaitLoad();
|
||||
return this.containedPaths.contains(new CachedResourcePath(paths));
|
||||
var node = this.root;
|
||||
for (String path : paths) {
|
||||
if (path.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
node = node.children.get(path);
|
||||
if (node == null) {
|
||||
//ModernFix.LOGGER.info("Does not have " + String.join("/", paths));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Collection<ResourceLocation> getResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate<ResourceLocation> filter) {
|
||||
public void collectResources(PackType type, String resourceNamespace, String[] components, int maxDepth, PackResources.ResourceOutput output) {
|
||||
if(!PackTypeHelper.isVanillaPackType(type))
|
||||
throw new IllegalArgumentException("Only vanilla PackTypes are supported");
|
||||
awaitLoad();
|
||||
List<CachedResourcePath> paths = resourceListings.get(type).getOrDefault(resourceNamespace, Collections.emptyList());
|
||||
if(paths.isEmpty())
|
||||
return Collections.emptyList();
|
||||
String testPath = pathIn.endsWith("/") ? pathIn : (pathIn + "/");
|
||||
ArrayList<ResourceLocation> resources = new ArrayList<>();
|
||||
for(CachedResourcePath cachePath : paths) {
|
||||
if((cachePath.getNameCount() - 2) > maxDepth)
|
||||
continue;
|
||||
String fullPath = cachePath.getFullPath(2);
|
||||
String fullTestPath = fullPath.endsWith("/") ? fullPath : (fullPath + "/");
|
||||
if(!fullTestPath.startsWith(testPath)) {
|
||||
continue;
|
||||
}
|
||||
ResourceLocation foundResource = ResourceLocation.fromNamespaceAndPath(resourceNamespace, fullPath);
|
||||
if(!filter.test(foundResource))
|
||||
continue;
|
||||
resources.add(foundResource);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private static final WeakHashMap<ICachingResourcePack, Boolean> cachingPacks = new WeakHashMap<>();
|
||||
public static void track(ICachingResourcePack pack) {
|
||||
synchronized (cachingPacks) {
|
||||
cachingPacks.put(pack, Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidate() {
|
||||
if(!ModernFixPlatformHooks.INSTANCE.isDevEnv())
|
||||
var node = this.root.getChild(type.getDirectory());
|
||||
if (node == null) {
|
||||
return;
|
||||
synchronized (cachingPacks) {
|
||||
cachingPacks.keySet().forEach(pack -> {
|
||||
if(pack != null)
|
||||
pack.invalidateCache();
|
||||
});
|
||||
}
|
||||
node = node.getChild(resourceNamespace);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.collectResources(resourceNamespace, this.rootPathsByType.get(type).resolve(resourceNamespace), components, 0, maxDepth, output);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,5 +56,8 @@ accessible field net/minecraft/client/renderer/entity/EnderDragonRenderer$Dragon
|
|||
accessible method net/minecraft/world/level/block/state/StateDefinition appendPropertyCodec (Lcom/mojang/serialization/MapCodec;Ljava/util/function/Supplier;Ljava/lang/String;Lnet/minecraft/world/level/block/state/properties/Property;)Lcom/mojang/serialization/MapCodec;
|
||||
accessible class net/minecraft/client/multiplayer/SessionSearchTrees$Key
|
||||
|
||||
accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$State preparationNanos Ljava/util/concurrent/atomic/AtomicLong;
|
||||
accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$State reloadNanos Ljava/util/concurrent/atomic/AtomicLong;
|
||||
|
||||
accessible class net/minecraft/world/item/crafting/Ingredient$Value
|
||||
accessible class net/minecraft/world/item/crafting/Ingredient$ItemValue
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package org.embeddedt.modernfix.neoforge.mixin.feature.measure_time;
|
||||
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(targets = "net/neoforged/neoforge/event/AddReloadListenerEvent$WrappedStateAwareListener")
|
||||
public abstract class AddReloadListenerEventWrapperMixin implements PreparableReloadListener {
|
||||
@Shadow @Final private PreparableReloadListener wrapped;
|
||||
|
||||
/**
|
||||
* @author embeddedt
|
||||
* @reason make a proper name show up in ProfiledReloadInstance
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.wrapped.getClass().getName();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user