mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-15 00:14:28 -04:00
Implement builtin mod resource/data pack sorting (#4070)
* builtin pack sorting 1.21.5
* Fix switch case indentation
* Revert "Fix switch case indentation"
This reverts commit 6714db0fa0
.
* Fix formatting
This commit is contained in:
parent
216530c8b2
commit
504c6f623c
16 changed files with 432 additions and 12 deletions
fabric-resource-loader-v0
build.gradle
src
main/java/net/fabricmc/fabric/impl/resource/loader
ModNioResourcePack.javaModResourcePackCreator.javaModResourcePackSorter.javaModResourcePackUtil.java
testmod
testmodA/resources
testmodB/resources
data/fabric-resource-loader-v0-testmod/recipe
fabric.mod.jsontestmodC/resources
|
@ -4,8 +4,55 @@ loom {
|
|||
accessWidenerPath = file("src/main/resources/fabric-resource-loader-v0.accesswidener")
|
||||
}
|
||||
|
||||
moduleDependencies(project, ['fabric-api-base'])
|
||||
|
||||
testDependencies(project, [
|
||||
':fabric-lifecycle-events-v1',
|
||||
':fabric-api-base',
|
||||
':fabric-gametest-api-v1',
|
||||
':fabric-resource-loader-v0'
|
||||
])
|
||||
|
||||
// Setup 3 test mods used for testing resource sorting
|
||||
sourceSets {
|
||||
testmodA
|
||||
testmodB
|
||||
testmodC
|
||||
}
|
||||
|
||||
[sourceSets.testmodA, sourceSets.testmodB, sourceSets.testmodC].each { sourceSet ->
|
||||
dependencies {
|
||||
testmodImplementation sourceSet.output
|
||||
}
|
||||
rootProject.dependencies {
|
||||
testmodImplementation sourceSet.output
|
||||
}
|
||||
|
||||
tasks.register("${sourceSet.name}Jar", Jar) {
|
||||
from sourceSet.output
|
||||
archiveBaseName.set(sourceSet.name)
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects.each { p ->
|
||||
if (p.extensions.findByName("loom") == null) {
|
||||
return // Skip over the meta projects
|
||||
}
|
||||
|
||||
p.loom.mods.register("fabric-resource-loader-v0-testmod-a") {
|
||||
sourceSet sourceSets.testmodA
|
||||
}
|
||||
p.loom.mods.register("fabric-resource-loader-v0-testmod-b") {
|
||||
sourceSet sourceSets.testmodB
|
||||
}
|
||||
p.loom.mods.register("fabric-resource-loader-v0-testmod-c") {
|
||||
sourceSet sourceSets.testmodC
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("remapTestmodJar", net.fabricmc.loom.task.RemapJarTask) {
|
||||
nestedJars.from(tasks.testmodAJar)
|
||||
nestedJars.from(tasks.testmodBJar)
|
||||
nestedJars.from(tasks.testmodCJar)
|
||||
addNestedDependencies = true
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ public class ModNioResourcePack implements ResourcePack, ModResourcePack {
|
|||
*/
|
||||
private final boolean modBundled;
|
||||
|
||||
@Nullable
|
||||
public static ModNioResourcePack create(String id, ModContainer mod, String subPath, ResourceType type, ResourcePackActivationType activationType, boolean modBundled) {
|
||||
List<Path> rootPaths = mod.getRootPaths();
|
||||
List<Path> paths;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package net.fabricmc.fabric.impl.resource.loader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
@ -35,6 +34,7 @@ import net.minecraft.resource.ResourceType;
|
|||
import net.minecraft.text.Text;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.ModResourcePack;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
/**
|
||||
* Represents a resource pack provider for mods and built-in mods resource packs.
|
||||
|
@ -134,8 +134,7 @@ public class ModResourcePackCreator implements ResourcePackProvider {
|
|||
}
|
||||
|
||||
private void registerModPack(Consumer<ResourcePackProfile> consumer, @Nullable String subPath, Predicate<Set<String>> parents) {
|
||||
List<ModResourcePack> packs = new ArrayList<>();
|
||||
ModResourcePackUtil.appendModResourcePacks(packs, this.type, subPath);
|
||||
List<ModResourcePack> packs = ModResourcePackUtil.getModResourcePacks(FabricLoader.getInstance(), this.type, subPath);
|
||||
|
||||
for (ModResourcePack pack : packs) {
|
||||
ResourcePackProfile profile = ResourcePackProfile.create(
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 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.resource.loader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import net.fabricmc.fabric.api.resource.ModResourcePack;
|
||||
import net.fabricmc.fabric.impl.base.toposort.NodeSorting;
|
||||
import net.fabricmc.fabric.impl.base.toposort.SortableNode;
|
||||
|
||||
public class ModResourcePackSorter {
|
||||
private final Object lock = new Object();
|
||||
private ModResourcePack[] packs;
|
||||
/**
|
||||
* Registered load phases.
|
||||
*/
|
||||
private final Map<String, LoadPhaseData> phases = new LinkedHashMap<>();
|
||||
/**
|
||||
* Phases sorted in the correct dependency order.
|
||||
*/
|
||||
private final List<LoadPhaseData> sortedPhases = new ArrayList<>();
|
||||
|
||||
ModResourcePackSorter() {
|
||||
this.packs = new ModResourcePack[0];
|
||||
}
|
||||
|
||||
public List<ModResourcePack> getPacks() {
|
||||
return Collections.unmodifiableList(Arrays.asList(this.packs));
|
||||
}
|
||||
|
||||
public void addPack(ModResourcePack pack) {
|
||||
Objects.requireNonNull(pack, "Can't register a null pack");
|
||||
|
||||
String modId = pack.getId();
|
||||
Objects.requireNonNull(modId, "Can't register a pack without a mod id");
|
||||
|
||||
synchronized (lock) {
|
||||
getOrCreatePhase(modId, true).addPack(pack);
|
||||
rebuildPackList(packs.length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private LoadPhaseData getOrCreatePhase(String id, boolean sortIfCreate) {
|
||||
LoadPhaseData phase = phases.get(id);
|
||||
|
||||
if (phase == null) {
|
||||
phase = new LoadPhaseData(id);
|
||||
phases.put(id, phase);
|
||||
sortedPhases.add(phase);
|
||||
|
||||
if (sortIfCreate) {
|
||||
NodeSorting.sort(sortedPhases, "mod resource packs", Comparator.comparing(data -> data.modId));
|
||||
}
|
||||
}
|
||||
|
||||
return phase;
|
||||
}
|
||||
|
||||
private void rebuildPackList(int newLength) {
|
||||
// Rebuild pack list.
|
||||
if (sortedPhases.size() == 1) {
|
||||
// Special case with a single phase: use the array of the phase directly.
|
||||
packs = sortedPhases.getFirst().packs;
|
||||
} else {
|
||||
ModResourcePack[] newHandlers = new ModResourcePack[newLength];
|
||||
int newHandlersIndex = 0;
|
||||
|
||||
for (LoadPhaseData existingPhase : sortedPhases) {
|
||||
int length = existingPhase.packs.length;
|
||||
System.arraycopy(existingPhase.packs, 0, newHandlers, newHandlersIndex, length);
|
||||
newHandlersIndex += length;
|
||||
}
|
||||
|
||||
packs = newHandlers;
|
||||
}
|
||||
}
|
||||
|
||||
public void addLoadOrdering(String firstPhase, String secondPhase, ModResourcePackUtil.Order order) {
|
||||
Objects.requireNonNull(firstPhase, "Tried to add an ordering for a null phase.");
|
||||
Objects.requireNonNull(secondPhase, "Tried to add an ordering for a null phase.");
|
||||
if (firstPhase.equals(secondPhase)) throw new IllegalArgumentException("Tried to add a phase that depends on itself.");
|
||||
|
||||
synchronized (lock) {
|
||||
LoadPhaseData first = getOrCreatePhase(firstPhase, false);
|
||||
LoadPhaseData second = getOrCreatePhase(secondPhase, false);
|
||||
|
||||
switch (order) {
|
||||
case BEFORE -> LoadPhaseData.link(first, second);
|
||||
case AFTER -> LoadPhaseData.link(second, first);
|
||||
}
|
||||
|
||||
NodeSorting.sort(this.sortedPhases, "event phases", Comparator.comparing(data -> data.modId));
|
||||
rebuildPackList(packs.length);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoadPhaseData extends SortableNode<LoadPhaseData> {
|
||||
final String modId;
|
||||
ModResourcePack[] packs;
|
||||
|
||||
LoadPhaseData(String modId) {
|
||||
this.modId = modId;
|
||||
this.packs = new ModResourcePack[0];
|
||||
}
|
||||
|
||||
void addPack(ModResourcePack pack) {
|
||||
int oldLength = packs.length;
|
||||
packs = Arrays.copyOf(packs, oldLength + 1);
|
||||
packs[oldLength] = pack;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDescription() {
|
||||
return modId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import java.io.InputStream;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -57,6 +58,7 @@ import net.fabricmc.fabric.api.resource.ModResourcePack;
|
|||
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.api.metadata.CustomValue;
|
||||
import net.fabricmc.loader.api.metadata.ModMetadata;
|
||||
|
||||
/**
|
||||
|
@ -65,29 +67,77 @@ import net.fabricmc.loader.api.metadata.ModMetadata;
|
|||
public final class ModResourcePackUtil {
|
||||
public static final Gson GSON = new Gson();
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModResourcePackUtil.class);
|
||||
private static final String LOAD_ORDER_KEY = "fabric:resource_load_order";
|
||||
|
||||
private ModResourcePackUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends mod resource packs to the given list.
|
||||
* Returns a list of mod resource packs.
|
||||
*
|
||||
* @param packs the resource pack list to append
|
||||
* @param type the type of resource
|
||||
* @param subPath the resource pack sub path directory in mods, may be {@code null}
|
||||
*/
|
||||
public static void appendModResourcePacks(List<ModResourcePack> packs, ResourceType type, @Nullable String subPath) {
|
||||
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
|
||||
if (container.getMetadata().getType().equals("builtin")) {
|
||||
public static List<ModResourcePack> getModResourcePacks(FabricLoader fabricLoader, ResourceType type, @Nullable String subPath) {
|
||||
ModResourcePackSorter sorter = new ModResourcePackSorter();
|
||||
|
||||
Collection<ModContainer> containers = fabricLoader.getAllMods();
|
||||
List<String> allIds = containers.stream().map(ModContainer::getMetadata).map(ModMetadata::getId).toList();
|
||||
|
||||
for (ModContainer container : containers) {
|
||||
ModMetadata metadata = container.getMetadata();
|
||||
String id = metadata.getId();
|
||||
|
||||
if (metadata.getType().equals("builtin")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ModResourcePack pack = ModNioResourcePack.create(container.getMetadata().getId(), container, subPath, type, ResourcePackActivationType.ALWAYS_ENABLED, true);
|
||||
ModResourcePack pack = ModNioResourcePack.create(id, container, subPath, type, ResourcePackActivationType.ALWAYS_ENABLED, true);
|
||||
|
||||
if (pack != null) {
|
||||
packs.add(pack);
|
||||
if (pack == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sorter.addPack(pack);
|
||||
|
||||
CustomValue loadOrder = metadata.getCustomValue(LOAD_ORDER_KEY);
|
||||
if (loadOrder == null) continue;
|
||||
|
||||
if (loadOrder.getType() == CustomValue.CvType.OBJECT) {
|
||||
CustomValue.CvObject object = loadOrder.getAsObject();
|
||||
|
||||
addLoadOrdering(object, allIds, sorter, Order.BEFORE, id);
|
||||
addLoadOrdering(object, allIds, sorter, Order.AFTER, id);
|
||||
} else {
|
||||
LOGGER.error("[Fabric] Resource load order should be an object");
|
||||
}
|
||||
}
|
||||
|
||||
return sorter.getPacks();
|
||||
}
|
||||
|
||||
public static void addLoadOrdering(CustomValue.CvObject object, List<String> allIds, ModResourcePackSorter sorter, Order order, String currentId) {
|
||||
List<String> modIds = new ArrayList<>();
|
||||
|
||||
CustomValue array = object.get(order.jsonKey);
|
||||
if (array == null) return;
|
||||
|
||||
switch (array.getType()) {
|
||||
case STRING -> modIds.add(array.getAsString());
|
||||
case ARRAY -> {
|
||||
for (CustomValue id : array.getAsArray()) {
|
||||
if (id.getType() == CustomValue.CvType.STRING) {
|
||||
modIds.add(id.getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
LOGGER.error("[Fabric] {} should be a string or an array", order.jsonKey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
modIds.stream().filter(allIds::contains).forEach(modId -> sorter.addLoadOrdering(modId, currentId, order));
|
||||
}
|
||||
|
||||
public static void refreshAutoEnabledPacks(List<ResourcePackProfile> enabledProfiles, Map<String, ResourcePackProfile> allProfiles) {
|
||||
|
@ -247,4 +297,15 @@ public final class ModResourcePackUtil {
|
|||
public static ResourcePackManager createClientManager() {
|
||||
return new ResourcePackManager(new VanillaDataPackProvider(new SymlinkFinder((path) -> true)), new ModResourcePackCreator(ResourceType.SERVER_DATA, true));
|
||||
}
|
||||
|
||||
public enum Order {
|
||||
BEFORE("before"),
|
||||
AFTER("after");
|
||||
|
||||
private final String jsonKey;
|
||||
|
||||
Order(String jsonKey) {
|
||||
this.jsonKey = jsonKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2016, 2017, 2018, 2019 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.test.resource.loader;
|
||||
|
||||
import net.minecraft.recipe.Recipe;
|
||||
import net.minecraft.recipe.ServerRecipeManager;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.test.TestContext;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import net.fabricmc.fabric.api.gametest.v1.GameTest;
|
||||
|
||||
public class BuiltinPackSortingTest {
|
||||
private static final String MOD_ID = "fabric-resource-loader-v0-testmod";
|
||||
|
||||
private static RegistryKey<Recipe<?>> recipe(String path) {
|
||||
return RegistryKey.of(RegistryKeys.RECIPE, Identifier.of(MOD_ID, path));
|
||||
}
|
||||
|
||||
@GameTest
|
||||
public void builtinPackSorting(TestContext context) {
|
||||
ServerRecipeManager manager = context.getWorld().getRecipeManager();
|
||||
|
||||
if (manager.get(recipe("disabled_by_b")).isPresent()) {
|
||||
throw context.createError(Text.literal("disabled_by_b recipe should not have been loaded."));
|
||||
}
|
||||
|
||||
if (manager.get(recipe("disabled_by_c")).isPresent()) {
|
||||
throw context.createError(Text.literal("disabled_by_c recipe should not have been loaded."));
|
||||
}
|
||||
|
||||
if (manager.get(recipe("enabled_by_c")).isEmpty()) {
|
||||
throw context.createError(Text.literal("enabled_by_c recipe should have been loaded."));
|
||||
}
|
||||
|
||||
long loadedRecipes = manager.values().stream().filter(r -> r.id().getValue().getNamespace().equals(MOD_ID)).count();
|
||||
context.assertTrue(loadedRecipes == 1, Text.literal("Unexpected loaded recipe count: " + loadedRecipes));
|
||||
context.complete();
|
||||
}
|
||||
}
|
|
@ -6,7 +6,10 @@
|
|||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabric-resource-loader-v0": "*"
|
||||
"fabric-resource-loader-v0": "*",
|
||||
"fabric-resource-loader-v0-testmod-a": "*",
|
||||
"fabric-resource-loader-v0-testmod-b": "*",
|
||||
"fabric-resource-loader-v0-testmod-c": "*"
|
||||
},
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
|
@ -16,6 +19,9 @@
|
|||
],
|
||||
"server": [
|
||||
"net.fabricmc.fabric.test.resource.loader.LanguageTestMod"
|
||||
],
|
||||
"fabric-gametest": [
|
||||
"net.fabricmc.fabric.test.resource.loader.BuiltinPackSortingTest"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shapeless",
|
||||
"category": "misc",
|
||||
"ingredients": [
|
||||
"minecraft:acacia_slab"
|
||||
],
|
||||
"result": {
|
||||
"id": "minecraft:acacia_boat",
|
||||
"count": 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-resource-loader-v0-testmod-a",
|
||||
"name": "Fabric Resource Loader (v0) Test Mod A",
|
||||
"version": "1.0.0",
|
||||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabric-resource-loader-v0": "*"
|
||||
},
|
||||
"custom": {
|
||||
"fabric:resource_load_order": {
|
||||
"before": [
|
||||
"fabric-resource-loader-v0-testmod-b",
|
||||
"mod-that-does-not-exist"
|
||||
],
|
||||
"after": [
|
||||
"mod-that-also-does-not-exist"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"fabric:load_conditions": [
|
||||
{
|
||||
"condition": "fabric:not",
|
||||
"value": {
|
||||
"condition": "fabric:true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shapeless",
|
||||
"category": "misc",
|
||||
"ingredients": [
|
||||
"minecraft:birch_slab"
|
||||
],
|
||||
"result": {
|
||||
"id": "minecraft:birch_boat",
|
||||
"count": 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"fabric:load_conditions": [
|
||||
{
|
||||
"condition": "fabric:not",
|
||||
"value": {
|
||||
"condition": "fabric:true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-resource-loader-v0-testmod-b",
|
||||
"name": "Fabric Resource Loader (v0) Test Mod B",
|
||||
"version": "1.0.0",
|
||||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabric-resource-loader-v0": "*"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"fabric:load_conditions": [
|
||||
{
|
||||
"condition": "fabric:not",
|
||||
"value": {
|
||||
"condition": "fabric:true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shapeless",
|
||||
"category": "misc",
|
||||
"ingredients": [
|
||||
"minecraft:oak_slab"
|
||||
],
|
||||
"result": {
|
||||
"id": "minecraft:oak_boat",
|
||||
"count": 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "fabric-resource-loader-v0-testmod-c",
|
||||
"name": "Fabric Resource Loader (v0) Test Mod C",
|
||||
"version": "1.0.0",
|
||||
"environment": "*",
|
||||
"license": "Apache-2.0",
|
||||
"depends": {
|
||||
"fabric-resource-loader-v0": "*"
|
||||
},
|
||||
"custom": {
|
||||
"fabric:resource_load_order": {
|
||||
"after": "fabric-resource-loader-v0-testmod-b"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue