(#72) Resource reloading hooks

This commit is contained in:
asie 2019-01-25 23:45:12 +01:00
parent 3a17200c4d
commit 2c3e78a357
9 changed files with 500 additions and 0 deletions

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.resource;
import net.minecraft.resource.ResourceReloadListener;
import net.minecraft.util.Identifier;
import java.util.Collection;
import java.util.Collections;
/**
* Interface for "identifiable" resource reload listeners.
*
* "Identifiable" listeners have an unique identifier, which can be depended on,
* and can provide dependencies that they would like to see executed before
* themselves.
*
* {@link ResourceReloadListenerKeys}
*/
public interface IdentifiableResourceReloadListener extends ResourceReloadListener {
/**
* @return The unique identifier of this listener.
*/
Identifier getFabricId();
/**
* @return The identifiers of listeners this listener expects to have been
* executed before itself.
*/
default Collection<Identifier> getFabricDependencies() {
return Collections.emptyList();
}
/**
* By default, resource reload listeners in Minecraft are all executed on
* the game's main thread while the game is paused. This means they do not
* need to provide any guarantees regarding their thread safety, or usage
* of resources potentially modified by other reload listeners.
*
* "Thread safety", in this context, refers simply to independence; namely
* whether or not, under the condition that all of its dependencies have
* already been processed, this resource reload listener can run without
* accessing or modifying areas it does not control in a non-thread-safe
* manner.
*
* @return Whether or not the listener can be executed in a thread-safe way.
*/
default boolean isListenerThreadSafe() {
return false;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.resource;
import net.fabricmc.fabric.impl.resources.ResourceManagerHelperImpl;
import net.minecraft.resource.ResourceManager;
import net.minecraft.resource.ResourceType;
/**
* Helper for working with {@link ResourceManager} instances.
*/
public interface ResourceManagerHelper {
/**
* Add a resource reload listener for a given registry.
* @param listener The resource reload listener.
*/
void addReloadListener(IdentifiableResourceReloadListener listener);
/**
* Get the ResourceManagerHelper instance for a given resource type.
* @param type The given resource type.
* @return The ResourceManagerHelper instance.
*/
static ResourceManagerHelper get(ResourceType type) {
return ResourceManagerHelperImpl.get(type);
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.resource;
import net.minecraft.util.Identifier;
/**
* This class contains default keys for various Minecraft resource reload listeners.
*
* {@link IdentifiableResourceReloadListener}
*/
public final class ResourceReloadListenerKeys {
// client
public static final Identifier SOUNDS = new Identifier("minecraft:sounds");
public static final Identifier FONTS = new Identifier("minecraft:fonts");
public static final Identifier MODELS = new Identifier("minecraft:models");
public static final Identifier LANGUAGES = new Identifier("minecraft:languages");
public static final Identifier TEXTURES = new Identifier("minecraft:textures");
// server
public static final Identifier TAGS = new Identifier("minecraft:tags");
public static final Identifier RECIPES = new Identifier("minecraft:recipes");
public static final Identifier ADVANCEMENTS = new Identifier("minecraft:advancements");
public static final Identifier FUNCTIONS = new Identifier("minecraft:functions");
public static final Identifier LOOT_TABLES = new Identifier("minecraft:loot_tables");
private ResourceReloadListenerKeys() {
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.resources;
import com.google.common.collect.Lists;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.resource.ResourceReloadListener;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.*;
public class ResourceManagerHelperImpl implements ResourceManagerHelper {
private static final Map<ResourceType, ResourceManagerHelperImpl> registryMap = new HashMap<>();
private static final Logger LOGGER = LogManager.getLogger();
private final List<IdentifiableResourceReloadListener> addedListeners = new ArrayList<>();
public static ResourceManagerHelper get(ResourceType type) {
return registryMap.computeIfAbsent(type, (t) -> new ResourceManagerHelperImpl());
}
public static void sort(ResourceType type, List<ResourceReloadListener> listeners) {
ResourceManagerHelperImpl instance = registryMap.get(type);
if (instance != null) {
instance.sort(listeners);
}
}
protected void sort(List<ResourceReloadListener> listeners) {
listeners.removeAll(addedListeners);
// General rules:
// - We *do not* touch the ordering of vanilla listeners. Ever.
// While dependency values are provided where possible, we cannot
// trust them 100%. Only code doesn't lie.
// - We addReloadListener all custom listeners after vanilla listeners. Same reasons.
List<IdentifiableResourceReloadListener> listenersToAdd = Lists.newArrayList(addedListeners);
Set<Identifier> resolvedIds = new HashSet<>();
for (ResourceReloadListener listener : listeners) {
if (listener instanceof IdentifiableResourceReloadListener) {
resolvedIds.add(((IdentifiableResourceReloadListener) listener).getFabricId());
}
}
int lastSize = -1;
while (listeners.size() != lastSize) {
lastSize = listeners.size();
Iterator<IdentifiableResourceReloadListener> it = listenersToAdd.iterator();
while (it.hasNext()) {
IdentifiableResourceReloadListener listener = it.next();
if (resolvedIds.containsAll(listener.getFabricDependencies())) {
resolvedIds.add(listener.getFabricId());
listeners.add(listener);
it.remove();
}
}
}
for (IdentifiableResourceReloadListener listener : listenersToAdd) {
LOGGER.warn("Could not resolve dependencies for listener: " + listener.getFabricId() + "!");
}
}
@Override
public void addReloadListener(IdentifiableResourceReloadListener listener) {
addedListeners.add(listener);
}
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.resources;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys;
import net.minecraft.client.audio.SoundLoader;
import net.minecraft.client.font.FontRendererManager;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.block.BlockRenderManager;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.client.resource.language.LanguageManager;
import net.minecraft.client.texture.TextureManager;
import net.minecraft.recipe.RecipeManager;
import net.minecraft.server.ServerAdvancementLoader;
import net.minecraft.server.function.CommandFunctionManager;
import net.minecraft.tag.TagManager;
import net.minecraft.util.Identifier;
import net.minecraft.world.loot.LootManager;
import org.spongepowered.asm.mixin.Mixin;
import java.util.Collection;
import java.util.Collections;
public class MixinKeyedResourceReloadListener {
@Mixin({
/* public */
SoundLoader.class, FontRendererManager.class, BakedModelManager.class, LanguageManager.class, TextureManager.class,
/* private */
WorldRenderer.class, BlockRenderManager.class, ItemRenderer.class
})
public static abstract class Client implements IdentifiableResourceReloadListener {
private Collection<Identifier> fabric_idDeps;
private Identifier fabric_id;
@Override
@SuppressWarnings({ "ConstantConditions", "RedundantCast" })
public Collection<Identifier> getFabricDependencies() {
if (fabric_idDeps == null) {
Object self = (Object) this;
if (self instanceof BakedModelManager || self instanceof WorldRenderer) {
fabric_idDeps = Collections.singletonList(ResourceReloadListenerKeys.TEXTURES);
} else if (self instanceof ItemRenderer || self instanceof BlockRenderManager) {
fabric_idDeps = Collections.singletonList(ResourceReloadListenerKeys.MODELS);
} else {
fabric_idDeps = Collections.emptyList();
}
}
return fabric_idDeps;
}
@Override
@SuppressWarnings({ "ConstantConditions", "RedundantCast" })
public Identifier getFabricId() {
if (fabric_id == null) {
Object self = (Object) this;
if (self instanceof SoundLoader) {
fabric_id = ResourceReloadListenerKeys.SOUNDS;
} else if (self instanceof FontRendererManager) {
fabric_id = ResourceReloadListenerKeys.FONTS;
} else if (self instanceof BakedModelManager) {
fabric_id = ResourceReloadListenerKeys.MODELS;
} else if (self instanceof LanguageManager) {
fabric_id = ResourceReloadListenerKeys.LANGUAGES;
} else if (self instanceof TextureManager) {
fabric_id = ResourceReloadListenerKeys.TEXTURES;
} else {
fabric_id = new Identifier("minecraft", "private/" + self.getClass().getSimpleName());
}
}
return fabric_id;
}
}
@Mixin({
/* public */
RecipeManager.class, ServerAdvancementLoader.class, CommandFunctionManager.class, LootManager.class, TagManager.class
/* private */
})
public static abstract class Server implements IdentifiableResourceReloadListener {
private Collection<Identifier> fabric_idDeps;
private Identifier fabric_id;
@Override
@SuppressWarnings({ "ConstantConditions", "RedundantCast" })
public Collection<Identifier> getFabricDependencies() {
if (fabric_idDeps == null) {
Object self = (Object) this;
if (self instanceof TagManager) {
fabric_idDeps = Collections.emptyList();
} else {
fabric_idDeps = Collections.singletonList(ResourceReloadListenerKeys.TAGS);
}
}
return fabric_idDeps;
}
@Override
@SuppressWarnings({ "ConstantConditions", "RedundantCast" })
public Identifier getFabricId() {
if (fabric_id == null) {
Object self = (Object) this;
;
if (self instanceof RecipeManager) {
fabric_id = ResourceReloadListenerKeys.RECIPES;
} else if (self instanceof ServerAdvancementLoader) {
fabric_id = ResourceReloadListenerKeys.ADVANCEMENTS;
} else if (self instanceof CommandFunctionManager) {
fabric_id = ResourceReloadListenerKeys.FUNCTIONS;
} else if (self instanceof LootManager) {
fabric_id = ResourceReloadListenerKeys.LOOT_TABLES;
} else if (self instanceof TagManager) {
fabric_id = ResourceReloadListenerKeys.TAGS;
} else {
fabric_id = new Identifier("minecraft", "private/" + self.getClass().getSimpleName());
}
}
return fabric_id;
}
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.resources;
import net.fabricmc.fabric.impl.resources.ResourceManagerHelperImpl;
import net.minecraft.resource.*;
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.util.List;
@Mixin(ReloadableResourceManagerImpl.class)
public class MixinReloadableResourceManagerImpl {
@Shadow
private List<ResourceReloadListener> listeners;
@Shadow
private ResourceType type;
@Inject(at = @At("HEAD"), method = "reload")
public void reload(List<ResourcePack> packs, CallbackInfo info) {
ResourceManagerHelperImpl.sort(type, listeners);
}
}

View file

@ -21,6 +21,7 @@
"registry.client.MixinBlockColorMap",
"registry.client.MixinItemColorMap",
"registry.client.MixinItemModelMap",
"resources.MixinKeyedResourceReloadListener$Client",
"resources.MixinMinecraftGame"
],
"injectors": {

View file

@ -27,7 +27,9 @@
"registry.MixinIdRegistry",
"registry.MixinServerPlayNetworkHandler",
"registry.MixinWorldSaveHandler",
"resources.MixinKeyedResourceReloadListener$Server",
"resources.MixinMinecraftServer",
"resources.MixinReloadableResourceManagerImpl",
"tools.MixinItemStack",
"tools.MixinMiningToolItem"
],

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2016, 2017, 2018 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.resources;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.resource.ResourceManager;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import java.util.Collection;
import java.util.Collections;
public class ResourceReloadModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ResourceManagerHelper.get(ResourceType.ASSETS).addReloadListener(new IdentifiableResourceReloadListener() {
@Override
public Identifier getFabricId() {
return new Identifier("fabric:rrmc2");
}
@Override
public Collection<Identifier> getFabricDependencies() {
return Collections.singletonList(new Identifier("fabric:rrmc1"));
}
@Override
public void onResourceReload(ResourceManager var1) {
System.out.println("Reloading (should run as #2)");
}
});
ResourceManagerHelper.get(ResourceType.ASSETS).addReloadListener(new IdentifiableResourceReloadListener() {
@Override
public Identifier getFabricId() {
return new Identifier("fabric:rrmc1");
}
@Override
public void onResourceReload(ResourceManager var1) {
System.out.println("Reloading (should run as #1)");
}
});
ResourceManagerHelper.get(ResourceType.ASSETS).addReloadListener(new IdentifiableResourceReloadListener() {
@Override
public Identifier getFabricId() {
return new Identifier("fabric:rrmc_should_not_resolve");
}
@Override
public Collection<Identifier> getFabricDependencies() {
return Collections.singletonList(new Identifier("fabric:rrmc_nonexistent"));
}
@Override
public void onResourceReload(ResourceManager var1) {
}
});
}
}