mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-12 08:17:35 +00:00
Hooks to allow registering and managing custom DFU schemes and types. (#8242)
This commit is contained in:
1 parent
643b972e97
commit
0ea593c50e
8 files changed
+1206
-2
No files matched your search
@@ -20,7 +20,9 @@
|
||||
package net.minecraftforge.common;
|
||||
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.datafix.ForgeDataFixerEventHandler;
|
||||
import net.minecraftforge.event.RegistryEvent;
|
||||
import net.minecraftforge.event.datafix.ConfigureDataFixSchemaEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.*;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
@@ -38,7 +40,8 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ForgeStatesProvider implements IModStateProvider {
|
||||
final ModLoadingState CREATE_REGISTRIES = ModLoadingState.withTransition("CREATE_REGISTRIES", "CONSTRUCT", ModLoadingPhase.GATHER, SerialTransition.of(()-> Stream.of(IModStateTransition.EventGenerator.fromFunction(RegistryEvent.NewRegistry::new))));
|
||||
final ModLoadingState SETUP_DFU = ModLoadingState.withTransition("SETUP_DFU", "CONSTRUCT", ModLoadingPhase.GATHER, SerialTransition.forDFU());
|
||||
final ModLoadingState CREATE_REGISTRIES = ModLoadingState.withTransition("CREATE_REGISTRIES", "SETUP_DFU", ModLoadingPhase.GATHER, SerialTransition.of(()-> Stream.of(IModStateTransition.EventGenerator.fromFunction(RegistryEvent.NewRegistry::new))));
|
||||
final ModLoadingState OBJECT_HOLDERS = ModLoadingState.withInline("OBJECT_HOLDERS", "CREATE_REGISTRIES", ModLoadingPhase.GATHER, ml-> ObjectHolderRegistry.findObjectHolders());
|
||||
final ModLoadingState INJECT_CAPABILITIES = ModLoadingState.withInline("INJECT_CAPABILITIES", "OBJECT_HOLDERS", ModLoadingPhase.GATHER, ml-> CapabilityManager.INSTANCE.injectCapabilities(ml.getAllScanData()));
|
||||
final ModLoadingState CUSTOM_TAGS = ModLoadingState.withInline("CUSTOM_TAGS", "INJECT_CAPABILITIES", ModLoadingPhase.GATHER, ml-> GameData.setCustomTagTypesFromRegistries());
|
||||
@@ -47,13 +50,23 @@ public class ForgeStatesProvider implements IModStateProvider {
|
||||
final ModLoadingState NETLOCK = ModLoadingState.withInline("NETWORK_LOCK", "FREEZE_DATA", ModLoadingPhase.COMPLETE, ml-> NetworkRegistry.lock());
|
||||
@Override
|
||||
public List<IModLoadingState> getAllStates() {
|
||||
return List.of(CREATE_REGISTRIES, OBJECT_HOLDERS, INJECT_CAPABILITIES, CUSTOM_TAGS, LOAD_REGISTRIES, FREEZE, NETLOCK);
|
||||
return List.of(SETUP_DFU, CREATE_REGISTRIES, OBJECT_HOLDERS, INJECT_CAPABILITIES, CUSTOM_TAGS, LOAD_REGISTRIES, FREEZE, NETLOCK);
|
||||
}
|
||||
|
||||
static record SerialTransition<T extends Event & IModBusEvent>(Supplier<Stream<EventGenerator<?>>> eventStream, BiFunction<Executor, ? extends EventGenerator<T>, CompletableFuture<List<Throwable>>> preDispatchHook, BiFunction<Executor, ? extends EventGenerator<T>, CompletableFuture<List<Throwable>>> postDispatchHook, BiFunction<Executor, CompletableFuture<List<Throwable>>, CompletableFuture<List<Throwable>>> finalActivityGenerator) implements IModStateTransition {
|
||||
public static <T extends Event & IModBusEvent> SerialTransition<T> of(Supplier<Stream<EventGenerator<?>>> eventStream) {
|
||||
return new SerialTransition<T>(eventStream, (t, f)->CompletableFuture.completedFuture(Collections.emptyList()), (t, f)->CompletableFuture.completedFuture(Collections.emptyList()), (e, prev) ->prev.thenApplyAsync(Function.identity(), e));
|
||||
}
|
||||
|
||||
private static SerialTransition<ConfigureDataFixSchemaEvent> forDFU() {
|
||||
return new SerialTransition<>(
|
||||
ForgeDataFixerEventHandler::generateConfigureEvents,
|
||||
ForgeDataFixerEventHandler::preDispatch,
|
||||
ForgeDataFixerEventHandler::postDispatch,
|
||||
ForgeDataFixerEventHandler::rebuildFixer
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
|
||||
return eventStream;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2021.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.datafix;
|
||||
|
||||
import com.mojang.datafixers.DataFix;
|
||||
import com.mojang.datafixers.TypeRewriteRule;
|
||||
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* This delegate allows for rebuilding of the cached rule, which is needed since it is
|
||||
* cached in {@link DataFix} and the recompute of schema will need to invalidate this cached
|
||||
* type rewrite rule.
|
||||
*/
|
||||
class ForgeDataFixDelegate extends DataFix
|
||||
{
|
||||
|
||||
private static final Method makeRuleMethod = ObfuscationReflectionHelper.findMethod(
|
||||
DataFix.class,
|
||||
"makeRule"
|
||||
);
|
||||
|
||||
private final DataFix wrapped;
|
||||
|
||||
@Nullable
|
||||
private TypeRewriteRule wrappedRule;
|
||||
|
||||
ForgeDataFixDelegate(final DataFix wrapped)
|
||||
{
|
||||
//We pass in dummy data to the super type.
|
||||
//However we can not pass in null to the super type for the output schema, since this would cause an NPE.
|
||||
//Luckily ForgeSchema has this option while preserving the version key.
|
||||
//So we pass it that, for good measure.
|
||||
super(new ForgeSchema(wrapped.getVersionKey(), null, null), false);
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeRewriteRule getRule()
|
||||
{
|
||||
//Check if OUR cached rule is available.
|
||||
if (this.wrappedRule == null)
|
||||
{
|
||||
//Not available get a new one.
|
||||
this.wrappedRule = rebuildRuleReflectively();
|
||||
}
|
||||
|
||||
//Cached version available, return it.
|
||||
return wrappedRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the rule using reflection.
|
||||
* This is needed since "makeRule" is protected in DataFix.
|
||||
* And "getRule" returns a potentially cached version, which is explicitly not what we want,
|
||||
* since that cached version might contain an older invalid schema from before a rebake.
|
||||
*
|
||||
* @return The type rewrite rule as defined by "makeRule" in the wrapped DataFix.
|
||||
*/
|
||||
private TypeRewriteRule rebuildRuleReflectively()
|
||||
{
|
||||
try
|
||||
{
|
||||
//Just invoke it via reflection.
|
||||
final Object candidate = makeRuleMethod.invoke(this.wrapped);
|
||||
//Check if we have a rule, this is just a security barrier to just crash hard if in the future the schema changes,
|
||||
//so that this is noted during the initial porting phase.
|
||||
if (candidate instanceof TypeRewriteRule typeRewriteRule)
|
||||
{
|
||||
return typeRewriteRule;
|
||||
}
|
||||
|
||||
//WTF Who put this here? Who is returning a none TypeRewriteRule from a method that should return it.!
|
||||
throw new IllegalStateException("Failed to get a rewrite rule. The returned object was not a TypeRewriteRule!"); //Should never be reached.
|
||||
}
|
||||
catch (IllegalAccessException | InvocationTargetException e)
|
||||
{
|
||||
throw new IllegalStateException("Could not get or invoke makeRule() Method on data fixer!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for resetting of the cached rule.
|
||||
*/
|
||||
public void resetRule()
|
||||
{
|
||||
this.wrappedRule = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped datafix originally cached rule.
|
||||
* Does not rebuild it!.
|
||||
*
|
||||
* Is also irrelevant since we are not using this value anyway and is only needed to prevent NPEs in the super class.
|
||||
*
|
||||
* @return The value.
|
||||
*/
|
||||
@Override
|
||||
protected TypeRewriteRule makeRule()
|
||||
{
|
||||
return wrapped.getRule();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2021.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.datafix;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mojang.datafixers.DataFixUtils;
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import com.mojang.datafixers.DataFixerBuilder;
|
||||
import com.mojang.datafixers.DataFixerUpper;
|
||||
import com.mojang.datafixers.DataFix;
|
||||
import com.mojang.datafixers.schemas.Schema;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* The purpose of this custom data fixer builder is to gain full control over the datafixer that is in play at runtime.
|
||||
* <p>
|
||||
* This means that schemas become adaptable and that the ruleset can be dynamically rebaked to adapt to the changes mod authors make to the schemas.
|
||||
* <p>
|
||||
* Some methods are carbon copies of the DFU counterpart, but are copied so that we can control the data.
|
||||
*/
|
||||
public class ForgeDataFixerBuilder extends DataFixerBuilder
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private final int dataVersion;
|
||||
private final List<ForgeSchema> allSchemas = new ArrayList<>();
|
||||
private final Int2ObjectSortedMap<ForgeSchema> schemas = new Int2ObjectAVLTreeMap<>();
|
||||
private final List<ForgeDataFixDelegate> globalList = Lists.newArrayList();
|
||||
private final IntSortedSet fixerVersions = new IntAVLTreeSet();
|
||||
|
||||
public ForgeDataFixerBuilder(final int dataVersion)
|
||||
{
|
||||
super(dataVersion);
|
||||
this.dataVersion = dataVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Schema addSchema(final int version, final int subVersion, final BiFunction<Integer, Schema, Schema> factory)
|
||||
{
|
||||
final int key = DataFixUtils.makeKey(version, subVersion);
|
||||
final Schema parent = schemas.isEmpty() ? null : schemas.get(getLowestSchemaSameVersion(schemas, key - 1));
|
||||
return addWrappedSchema(factory.apply(key, parent), parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSchema(final Schema schema)
|
||||
{
|
||||
final Schema parent = schemas.isEmpty() ? null : schemas.get(getLowestSchemaSameVersion(schemas, schema.getVersionKey() - 1));
|
||||
addWrappedSchema(schema, parent);
|
||||
}
|
||||
|
||||
private ForgeSchema addWrappedSchema(final Schema schema, final Schema parent)
|
||||
{
|
||||
final ForgeSchema wrapped = new ForgeSchema(schema.getVersionKey(), parent, schema);
|
||||
schemas.put(wrapped.getVersionKey(), wrapped);
|
||||
allSchemas.add(wrapped);
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFixer build(final Executor executor)
|
||||
{
|
||||
return new ForgeDataFixerDelegateHandler(dataVersion, schemas, allSchemas, globalList, executor, fixerVersions);
|
||||
}
|
||||
|
||||
/**
|
||||
* CC of the original method in {@link DataFixerUpper}. Is protected so not accessible for us.
|
||||
*
|
||||
* @param schemas The schemas to search in.
|
||||
* @param versionKey The version to find the lowest matching schema for.
|
||||
* @return The version for the lowest matching schema.
|
||||
*/
|
||||
protected static int getLowestSchemaSameVersion(final Int2ObjectSortedMap<ForgeSchema> schemas, final int versionKey)
|
||||
{
|
||||
if (versionKey < schemas.firstIntKey())
|
||||
{
|
||||
return schemas.firstIntKey();
|
||||
}
|
||||
return schemas.subMap(0, versionKey + 1).lastIntKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFixer(final DataFix fix)
|
||||
{
|
||||
final int version = DataFixUtils.getVersion(fix.getVersionKey());
|
||||
|
||||
if (version > dataVersion)
|
||||
{
|
||||
LOGGER.warn("Ignored fix registered for version: {} as the DataVersion of the game is: {}", version, dataVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
globalList.add(new ForgeDataFixDelegate(fix));
|
||||
fixerVersions.add(fix.getVersionKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2021.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.datafix;
|
||||
|
||||
import com.mojang.datafixers.DataFixUtils;
|
||||
import com.mojang.datafixers.DataFixerUpper;
|
||||
import com.mojang.datafixers.DataFix;
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.TypeRewriteRule;
|
||||
import com.mojang.datafixers.schemas.Schema;
|
||||
import com.mojang.datafixers.types.Type;
|
||||
import com.mojang.serialization.Dynamic;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps;
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSets;
|
||||
import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A delegate handler which can rebuild a data fixer on the fly.
|
||||
*/
|
||||
class ForgeDataFixerDelegateHandler extends DataFixerUpper
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private final int dataVersion;
|
||||
private final List<ForgeSchema> allSchemas;
|
||||
private final Int2ObjectSortedMap<ForgeSchema> schemas;
|
||||
private final List<ForgeDataFixDelegate> globalList;
|
||||
private final IntSortedSet fixerVersions;
|
||||
|
||||
private InnerFixer activeFixer;
|
||||
|
||||
ForgeDataFixerDelegateHandler(
|
||||
final int dataVersion,
|
||||
final Int2ObjectSortedMap<ForgeSchema> schemas,
|
||||
final List<ForgeSchema> allSchemas,
|
||||
final List<ForgeDataFixDelegate> globalList,
|
||||
final Executor executor,
|
||||
final IntSortedSet fixerVersions
|
||||
)
|
||||
{
|
||||
//Supply dummy data to the super constructor
|
||||
//We never invoke and method on the super and so this data does not matter.
|
||||
super(Int2ObjectSortedMaps.emptyMap(), Collections.emptyList(), IntSortedSets.EMPTY_SET);
|
||||
|
||||
//Store the builders input so we can rebuild it later if needed.
|
||||
this.dataVersion = dataVersion;
|
||||
this.allSchemas = Collections.unmodifiableList(allSchemas);
|
||||
this.schemas = schemas;
|
||||
this.globalList = globalList;
|
||||
this.fixerVersions = fixerVersions;
|
||||
|
||||
//Rebuild the setup immediately.
|
||||
rebuildFixer(executor);
|
||||
}
|
||||
|
||||
public void rebuildFixer(final Executor executor)
|
||||
{
|
||||
//Reset our rules, normal vanilla rules (ours too) keep a cache of their schemas
|
||||
//We can not use that and need to forcefully recompute them.
|
||||
this.globalList.forEach(ForgeDataFixDelegate::resetRule);
|
||||
|
||||
//All schemas are already reset via the mod event bus as this time.
|
||||
//Rebuild of those is not needed.
|
||||
this.activeFixer = new InnerFixer(new Int2ObjectAVLTreeMap<Schema>(schemas), new ArrayList<>(globalList), new IntAVLTreeSet(fixerVersions));
|
||||
|
||||
//This section was added to make it easier to debug the rule set at runtime, since it is no longer pre-computed off thread,
|
||||
//But only on the main thread, at the moment it is needed.
|
||||
//This basically disables the REWRITE_CACHE prefill and fills it on-demand.
|
||||
//Although it is mainly used for debugging, it also provides a good way of reducing DFU's memory footprint, when it is not needed.
|
||||
//Since the following code will compute an update rule from every version to the current for every known type in the schema,
|
||||
//relating to this version. By default, however this is disabled to preserve vanilla compatibility, since it does do the pre-compute
|
||||
//to fill up the cache.
|
||||
//To enable it start the JVM with the system property set to true, -Dforge.datafixer.disablePreCompute=true needs to be added as
|
||||
//a JVM Launch argument.
|
||||
if (Boolean.getBoolean("forge.datafixer.disablePreCompute"))
|
||||
{
|
||||
LOGGER.warn("The pre-compute of the data fixer rules is disabled, this is not recommended for normal use, but can be useful for debugging or in low-memory situations.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Get an iterator for all known versions.
|
||||
final IntBidirectionalIterator iterator = activeFixer.fixerVersions().iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
//Grab the key.
|
||||
final int versionKey = iterator.nextInt();
|
||||
|
||||
//Grab its schema.
|
||||
final Schema schema = schemas.get(versionKey);
|
||||
|
||||
//For each type in the schema, compute its rewrite rule for the current version.
|
||||
for (final String typeName : schema.types())
|
||||
{
|
||||
//Schedule the recompute off-thread
|
||||
CompletableFuture.runAsync(() -> {
|
||||
//Grab the type.
|
||||
final Type<?> dataType = schema.getType(() -> typeName);
|
||||
|
||||
//Grab its normal rewrite rule for the given version and our current target version.
|
||||
final TypeRewriteRule rule = activeFixer.getRule(DataFixUtils.getVersion(versionKey), dataVersion);
|
||||
|
||||
//Optimize the rule to only apply to the given type.
|
||||
dataType.rewrite(rule, DataFixerUpper.OPTIMIZATION_RULE);
|
||||
}, executor).exceptionally(e -> {
|
||||
//Fails.
|
||||
LOGGER.error("Unable to build datafixers", e);
|
||||
|
||||
//Just kill it, if this fails we have a problem anyway!
|
||||
Runtime.getRuntime().exit(1);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Dynamic<T> update(
|
||||
final DSL.TypeReference type, final Dynamic<T> input, final int version, final int newVersion
|
||||
)
|
||||
{
|
||||
//Invoke the internal DFU engine.
|
||||
return activeFixer.update(
|
||||
type, input, version, newVersion
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Schema getSchema(final int key)
|
||||
{
|
||||
//Grab it from the internal DFU engine.
|
||||
return activeFixer.getSchema(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives access to all known schemas at the point of building of this wrapper.
|
||||
*
|
||||
* @return All schemas known to DFU.
|
||||
*/
|
||||
public List<ForgeSchema> getAllSchemas()
|
||||
{
|
||||
return allSchemas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a DFU that gives access to the protected sub methods so data can be properly extracted.
|
||||
* Delegates all of its method invocations to the super DFU instance.
|
||||
*/
|
||||
private static final class InnerFixer extends DataFixerUpper
|
||||
{
|
||||
private InnerFixer(final Int2ObjectSortedMap<Schema> schemas, final List<DataFix> globalList, final IntSortedSet fixerVersions)
|
||||
{
|
||||
super(schemas, globalList, fixerVersions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Dynamic<T> update(final DSL.TypeReference type, final Dynamic<T> input, final int version, final int newVersion)
|
||||
{
|
||||
return super.update(type, input, version, newVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Schema getSchema(final int key)
|
||||
{
|
||||
return super.getSchema(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<?> getType(final DSL.TypeReference type, final int version)
|
||||
{
|
||||
return super.getType(type, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeRewriteRule getRule(final int version, final int dataVersion)
|
||||
{
|
||||
return super.getRule(version, dataVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntSortedSet fixerVersions()
|
||||
{
|
||||
return super.fixerVersions();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2021.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.datafix;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mojang.datafixers.schemas.Schema;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.util.datafix.DataFixers;
|
||||
import net.minecraftforge.event.datafix.ConfigureDataFixSchemaEvent;
|
||||
import net.minecraftforge.fml.IModStateTransition;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Event handler utility class for managing the events related to data fixers.
|
||||
* This particular class is used to handle firing of the parallel mod loading events
|
||||
* for the DFU setup.
|
||||
*
|
||||
* These events are fired immediately after mod construction and before the rest of the game
|
||||
* loads, even before registries are fired.
|
||||
*
|
||||
* THIS CLASS IS FOR INTERNAL USE ONLY!
|
||||
*/
|
||||
public class ForgeDataFixerEventHandler
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
/**
|
||||
* Gives access to the list of schemas registered with the data fixers.
|
||||
* If we are currently running an incompatible version of DFU or somebody messed with it,
|
||||
* then an empty list is returned.
|
||||
*
|
||||
* @return The list of all known schemas to handle.
|
||||
*/
|
||||
private static List<ForgeSchema> getAllSchemas()
|
||||
{
|
||||
//Check if we are running in a compatible environment.
|
||||
if (DataFixers.getDataFixer() instanceof ForgeDataFixerDelegateHandler forgeDataFixerDelegateHandler)
|
||||
{
|
||||
//Forge DFU wrapper is active, grab its configured schemas.
|
||||
return forgeDataFixerDelegateHandler.getAllSchemas();
|
||||
}
|
||||
|
||||
//Incompatible environment, return an empty list.
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder function for creating the events that are fired on start up.
|
||||
* This returns one generator per schema, and keeps the schema in the order defined.
|
||||
*
|
||||
* The generator will always return the same multi-thread capable event reference regardless
|
||||
* of the passed in mod name.
|
||||
*
|
||||
* @return The stream of event generators used to fire one event per mod per schema.
|
||||
*/
|
||||
public static Stream<IModStateTransition.EventGenerator<?>> generateConfigureEvents()
|
||||
{
|
||||
//Grab the schemas
|
||||
List<ForgeSchema> schemas = Lists.newArrayList(getAllSchemas());
|
||||
//Sort them based on their version key.
|
||||
schemas.sort(Comparator.comparingInt(Schema::getVersionKey));
|
||||
|
||||
//Make one event per schema, and return it reference based from the event generator,
|
||||
//regardless of which mod the generator is invoked for.
|
||||
return schemas.stream()
|
||||
.map(ConfigureDataFixSchemaEvent::new)
|
||||
.map(event -> IModStateTransition.EventGenerator.fromFunction(modContainer -> event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by the event management system to indicate that we are about the fire an event for the given generator to all mods.
|
||||
* This is invoked exactly once, and only once, per generator (and as such per event, and schema).
|
||||
*
|
||||
* Clears out the current status of the schema by resetting its type definition maps.
|
||||
*
|
||||
* @param executor The executor to execute this task on.
|
||||
* @param eventGenerator The event generator that is about to be executed.
|
||||
* @return A future that indicates success or failure.
|
||||
*/
|
||||
public static CompletableFuture<List<Throwable>> preDispatch(
|
||||
final Executor executor, final IModStateTransition.EventGenerator<ConfigureDataFixSchemaEvent> eventGenerator
|
||||
)
|
||||
{
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
//Grab the event, with a null container (is reference equals anyways for all containers).
|
||||
final ConfigureDataFixSchemaEvent event = eventGenerator.apply(null);
|
||||
|
||||
//Log that we are processing that particular event.
|
||||
LOGGER.debug("Registering DFU Schema updates: {}", event.getSchema().getVersionKey());
|
||||
|
||||
//Check if we are a compatible schema.
|
||||
if (event.getSchema() instanceof ForgeSchema forgeSchema)
|
||||
{
|
||||
//And reset if needed.
|
||||
forgeSchema.resetSchema();
|
||||
}
|
||||
}, executor).thenApply(v -> Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by the event management system to indicate that it has fired all the events for the given schema.
|
||||
* This allows us to collect the data from the events and trigger the given schema to rebuild its type map.
|
||||
*
|
||||
* @param executor The executor to execute the task on.
|
||||
* @param eventGenerator The event generator for the event (and thus the schema in question).
|
||||
* @return The future that indicates success or failure.
|
||||
*/
|
||||
public static CompletableFuture<List<Throwable>> postDispatch(
|
||||
final Executor executor, final IModStateTransition.EventGenerator<ConfigureDataFixSchemaEvent> eventGenerator
|
||||
)
|
||||
{
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
//Grab the event again, with a null container (is reference equals anyways for all containers).
|
||||
final ConfigureDataFixSchemaEvent event = eventGenerator.apply(null);
|
||||
|
||||
//Notify about the completion of the event firing.
|
||||
LOGGER.debug("Registered DFU Schema updates: {}", event.getSchema().getVersionKey());
|
||||
|
||||
//Check for a compatible schema.
|
||||
if (event.getSchema() instanceof ForgeSchema forgeSchema)
|
||||
{
|
||||
//Rebuild the schema with the mod data.
|
||||
forgeSchema.rebuildSchema(
|
||||
event.getEntityTypes(),
|
||||
event.getBlockEntityTypes()
|
||||
);
|
||||
}
|
||||
}, executor).handle((v, t) -> t != null ? Collections.singletonList(t) : Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by the event management system to indicate that it completely fired all events from the event stream.
|
||||
* This indicates that all the events for all the schemas have been fired and post processed.
|
||||
* As such we can now safely reconfigure the DFU wrapper so that the new schema configurations are picked
|
||||
* up by the system.
|
||||
*
|
||||
* @param executor The executor to execute the task on.
|
||||
* @param listCompletableFuture The list of previous failures.
|
||||
* @return The future which indicates success or failure.
|
||||
*/
|
||||
public static CompletableFuture<List<Throwable>> rebuildFixer(
|
||||
final Executor executor, final CompletableFuture<List<Throwable>> listCompletableFuture
|
||||
)
|
||||
{
|
||||
return listCompletableFuture.whenCompleteAsync((errors, except) -> {
|
||||
//Check for errors.
|
||||
if (except != null)
|
||||
{
|
||||
//Ouch that failed, log it and escape.
|
||||
LOGGER.fatal("Detected errors during dfu setup. DFU might not be available.", except);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Success, check for a compatible DFU instance and then rebuild it.
|
||||
if (DataFixers.getDataFixer() instanceof ForgeDataFixerDelegateHandler forgeDataFixerDelegateHandler)
|
||||
{
|
||||
//Do not do this on the mod executor!
|
||||
//This will crash the game.
|
||||
//Rebuild the DFU wrapper on the bootstrap executor
|
||||
forgeDataFixerDelegateHandler.rebuildFixer(Util.bootstrapExecutor());
|
||||
}
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2021.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.datafix;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.DataFixUtils;
|
||||
import com.mojang.datafixers.schemas.Schema;
|
||||
import com.mojang.datafixers.types.Type;
|
||||
import com.mojang.datafixers.types.families.RecursiveTypeFamily;
|
||||
import com.mojang.datafixers.types.families.TypeFamily;
|
||||
import com.mojang.datafixers.types.templates.RecursivePoint;
|
||||
import com.mojang.datafixers.types.templates.TypeTemplate;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A partial carbon copy of the {@link Schema} class.
|
||||
* But this allows us to delay the setup of the type hierarchy until we need it.
|
||||
*/
|
||||
class ForgeSchema extends Schema
|
||||
{
|
||||
//Can not be final, needs to be initialized on the fly!
|
||||
//Constructor invocation order and the what not!
|
||||
private Map<String, Supplier<TypeTemplate>> TYPE_TEMPLATES = Maps.newConcurrentMap();
|
||||
private Map<String, Type<?>> TYPES = Maps.newConcurrentMap();
|
||||
|
||||
private final Map<String, Integer> RECURSIVE_TYPES = Maps.newConcurrentMap();
|
||||
private final Map<String, Supplier<TypeTemplate>> MODDED_ENTITY_TYPES = Maps.newConcurrentMap();
|
||||
private final Map<String, Supplier<TypeTemplate>> MODDED_BLOCK_ENTITY_TYPES = Maps.newConcurrentMap();
|
||||
|
||||
//Store some data we can not access later anymore.
|
||||
private final String name;
|
||||
private final Schema wrapped;
|
||||
|
||||
ForgeSchema(final int versionKey, final Schema parent, final Schema wrapped)
|
||||
{
|
||||
super(versionKey, parent);
|
||||
this.wrapped = wrapped;
|
||||
|
||||
final int subVersion = DataFixUtils.getSubVersion(versionKey);
|
||||
name = "V" + DataFixUtils.getVersion(versionKey) + (subVersion == 0 ? "" : "." + subVersion);
|
||||
|
||||
//Now with the vanilla data in the schema, lets build our first version again.
|
||||
//Yes this executes the type resolve multiple times per schema (but it's not a big deal)
|
||||
resetSchema();
|
||||
rebuildSchema(
|
||||
Collections.emptyMap(),
|
||||
Collections.emptyMap()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the types, with support for the case that no types exists.
|
||||
* (Which should never happen, but you never know!)
|
||||
*
|
||||
* @return The type map.
|
||||
*/
|
||||
protected Map<String, Type<?>> buildTypes()
|
||||
{
|
||||
//Result map.
|
||||
final Map<String, Type<?>> types = Maps.newConcurrentMap();
|
||||
|
||||
//All the core templates into which recursion of data is possible.
|
||||
final List<TypeTemplate> templates = Lists.newArrayList();
|
||||
|
||||
//Check all recursion types
|
||||
for (final Map.Entry<String, Integer> entry : RECURSIVE_TYPES.entrySet())
|
||||
{
|
||||
//Generate a template for the type.
|
||||
templates.add(DSL.check(entry.getKey(), entry.getValue(), getTemplate(entry.getKey())));
|
||||
}
|
||||
|
||||
//Merge them all together into one root recursive type.
|
||||
final Optional<TypeTemplate> availableTypeTemplate = templates.stream().reduce(DSL::or);
|
||||
if (availableTypeTemplate.isEmpty())
|
||||
{
|
||||
//Check for its existence or nuke.
|
||||
return Maps.newConcurrentMap();
|
||||
}
|
||||
//Build a type family for our recursive types.
|
||||
final TypeTemplate choice = availableTypeTemplate.get();
|
||||
final TypeFamily family = new RecursiveTypeFamily(name, choice);
|
||||
|
||||
//Now process all other known types, taking care of recursion.
|
||||
for (final String name : TYPE_TEMPLATES.keySet())
|
||||
{
|
||||
final Type<?> type;
|
||||
final int recurseId = RECURSIVE_TYPES.getOrDefault(name, -1);
|
||||
if (recurseId != -1)
|
||||
{
|
||||
type = family.apply(recurseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
type = getTemplate(name).apply(family).apply(-1);
|
||||
}
|
||||
types.put(name, type);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> types()
|
||||
{
|
||||
return TYPES.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<?> getTypeRaw(final DSL.TypeReference type)
|
||||
{
|
||||
final String name = type.typeName();
|
||||
return TYPES.computeIfAbsent(name, key -> {
|
||||
throw new IllegalArgumentException("Unknown type: " + name);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<?> getType(final DSL.TypeReference type)
|
||||
{
|
||||
final String name = type.typeName();
|
||||
final Type<?> type1 = TYPES.computeIfAbsent(name, key -> {
|
||||
throw new IllegalArgumentException("Unknown type: " + name);
|
||||
});
|
||||
if (type1 instanceof RecursivePoint.RecursivePointType<?>)
|
||||
{
|
||||
return type1.findCheckedType(-1).orElseThrow(() -> new IllegalStateException("Could not find choice type in the recursive type"));
|
||||
}
|
||||
return type1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeTemplate resolveTemplate(final String name)
|
||||
{
|
||||
return TYPE_TEMPLATES.getOrDefault(name, () -> {
|
||||
throw new IllegalArgumentException("Unknown type: " + name);
|
||||
}).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeTemplate id(final String name)
|
||||
{
|
||||
final int id = RECURSIVE_TYPES.getOrDefault(name, -1);
|
||||
if (id != -1)
|
||||
{
|
||||
return DSL.id(id);
|
||||
}
|
||||
return getTemplate(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTypes(
|
||||
final Schema schema, final Map<String, Supplier<TypeTemplate>> entityTypes, final Map<String, Supplier<TypeTemplate>> blockEntityTypes
|
||||
)
|
||||
{
|
||||
//This is a bit wonky, in vanilla it suffices to register the types to one single version
|
||||
//And all newer versions will have this type.
|
||||
//Due to the way we set up the schemas with events later, that is not possible anymore.
|
||||
//Modders will need to register their types on all versions that they should exist in,
|
||||
//not just in the oldest version.
|
||||
|
||||
if (wrapped != null)
|
||||
{
|
||||
wrapped.registerTypes(schema, entityTypes, blockEntityTypes);
|
||||
}
|
||||
else if (parent != null)
|
||||
{
|
||||
parent.registerTypes(schema, entityTypes, blockEntityTypes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Supplier<TypeTemplate>> registerEntities(final Schema schema)
|
||||
{
|
||||
//This is a bit wonky, in vanilla it suffices to register the types to one single version
|
||||
//And all newer versions will have this type.
|
||||
//Due to the way we set up the schemas with events later, that is not possible anymore.
|
||||
//Modders will need to register their types on all versions that they should exist in,
|
||||
//not just in the oldest version.
|
||||
|
||||
if (wrapped != null)
|
||||
{
|
||||
return wrapped.registerEntities(schema);
|
||||
}
|
||||
if (parent != null)
|
||||
{
|
||||
return parent.registerEntities(schema);
|
||||
}
|
||||
|
||||
return Maps.newHashMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Supplier<TypeTemplate>> registerBlockEntities(final Schema schema)
|
||||
{
|
||||
//This is a bit wonky, in vanilla it suffices to register the types to one single version
|
||||
//And all newer versions will have this type.
|
||||
//Due to the way we set up the schemas with events later, that is not possible anymore.
|
||||
//Modders will need to register their types on all versions that they should exist in,
|
||||
//not just in the oldest version.
|
||||
|
||||
if (wrapped != null)
|
||||
{
|
||||
return wrapped.registerBlockEntities(schema);
|
||||
}
|
||||
if (parent != null)
|
||||
{
|
||||
return parent.registerBlockEntities(schema);
|
||||
}
|
||||
|
||||
return Maps.newHashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new type to the Schema with its type template.
|
||||
*
|
||||
* @param recursive Indicates if the type is potentially recursive.
|
||||
* @param type The type in question.
|
||||
* @param template Its template.
|
||||
*/
|
||||
@Override
|
||||
public void registerType(final boolean recursive, final DSL.TypeReference type, final Supplier<TypeTemplate> template)
|
||||
{
|
||||
//This is invoked from the super constructor, so our field init code has not run yet.
|
||||
//Set it to a none null value, so the super type can do its initial construction.
|
||||
if (TYPE_TEMPLATES == null)
|
||||
{
|
||||
TYPE_TEMPLATES = Maps.newHashMap();
|
||||
}
|
||||
|
||||
TYPE_TEMPLATES.put(type.typeName(), template);
|
||||
if (recursive && !RECURSIVE_TYPES.containsKey(type.typeName()))
|
||||
{
|
||||
RECURSIVE_TYPES.put(type.typeName(), RECURSIVE_TYPES.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the schema so that the type map can be properly rebuild.
|
||||
*/
|
||||
void resetSchema()
|
||||
{
|
||||
this.TYPE_TEMPLATES.clear();
|
||||
this.TYPES.clear();
|
||||
this.RECURSIVE_TYPES.clear();
|
||||
this.MODDED_ENTITY_TYPES.clear();
|
||||
this.MODDED_BLOCK_ENTITY_TYPES.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the schema's type map.
|
||||
* This also injects custom types for entities and block entities.
|
||||
*
|
||||
* @param modEntityTypes The mod entity types to inject.
|
||||
* @param modBlockEntityTypes The block entity types to inject.
|
||||
*/
|
||||
void rebuildSchema(
|
||||
final Map<String, Supplier<TypeTemplate>> modEntityTypes,
|
||||
final Map<String, Supplier<TypeTemplate>> modBlockEntityTypes
|
||||
)
|
||||
{
|
||||
//First grab the parent's additional modded types, if the parent is compatible.
|
||||
if (this.parent instanceof ForgeSchema parentForgeSchema)
|
||||
{
|
||||
//Compatible parent found, add its modded types.
|
||||
this.MODDED_ENTITY_TYPES.putAll(parentForgeSchema.MODDED_ENTITY_TYPES);
|
||||
this.MODDED_BLOCK_ENTITY_TYPES.putAll(parentForgeSchema.MODDED_BLOCK_ENTITY_TYPES);
|
||||
}
|
||||
|
||||
//Now add our own modded types, and potentially override the data.
|
||||
this.MODDED_ENTITY_TYPES.putAll(modEntityTypes);
|
||||
this.MODDED_BLOCK_ENTITY_TYPES.putAll(modBlockEntityTypes);
|
||||
|
||||
//Remove those types from the map, which the event marked for removal.
|
||||
this.MODDED_ENTITY_TYPES.values().removeIf(Objects::isNull);
|
||||
this.MODDED_BLOCK_ENTITY_TYPES.values().removeIf(Objects::isNull);
|
||||
|
||||
//Grab the vanilla entities.
|
||||
final Map<String, Supplier<TypeTemplate>> entityTypes = registerEntities(this);
|
||||
final Map<String, Supplier<TypeTemplate>> blockEntityTypes = registerBlockEntities(this);
|
||||
|
||||
//Add all modded entities and block entities.
|
||||
entityTypes.putAll(this.MODDED_ENTITY_TYPES);
|
||||
blockEntityTypes.putAll(this.MODDED_BLOCK_ENTITY_TYPES);
|
||||
|
||||
//Re-register them.
|
||||
registerTypes(this, entityTypes, blockEntityTypes);
|
||||
|
||||
//Build the type map again.
|
||||
TYPES = buildTypes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2021.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.event.datafix;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.datafixers.DSL;
|
||||
import com.mojang.datafixers.schemas.Schema;
|
||||
import com.mojang.datafixers.types.templates.TypeTemplate;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.datafix.fixes.References;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Event fired in parallel on the mod event bus to allow for the modification
|
||||
* of a DFU Schemas type signature.
|
||||
*
|
||||
* If used to register a new entity or block entity to a particular version,
|
||||
* then this also will need to be registered to all newer versions to persist properly!
|
||||
* As opposed to vanilla which automatically transfers the entities and block entities from older
|
||||
* schemas to newer ones, this event does not do that and the modder is on their own to implement this properly.
|
||||
*
|
||||
* We suggest instead of checking for version equality to check for a greater or equal version, and to unregister (or override with a newer version)
|
||||
* when the type changes again in a newer version.
|
||||
*
|
||||
* If a DFU touches a vanilla type (like a list of block entities in a chunk), then all the possible variations of that type need to be known,
|
||||
* in other words, when mojang changes the type signature of a vanilla type, all the mods that touch that type need also to register their types in that schema to persist.
|
||||
* An example of this is when Mojang moved the block entity list in the chunk during the upgrade from 1.17 to 1.18, any block entity type which is
|
||||
* not registered to the 1.18 schema will be lost, since the rule considers a not registered block entity type, as a type that got removed from the game.
|
||||
*/
|
||||
public class ConfigureDataFixSchemaEvent extends Event implements IModBusEvent
|
||||
{
|
||||
|
||||
private final Schema schema;
|
||||
|
||||
private final Map<String, Supplier<TypeTemplate>> entityTypes = Maps.newConcurrentMap();
|
||||
private final Map<String, Supplier<TypeTemplate>> blockEntityTypes = Maps.newConcurrentMap();
|
||||
|
||||
//Some unmodifiable views of the above maps
|
||||
private final Map<String, Supplier<TypeTemplate>> entityTypesView = Collections.unmodifiableMap(entityTypes);
|
||||
private final Map<String, Supplier<TypeTemplate>> blockEntityTypesView = Collections.unmodifiableMap(blockEntityTypes);
|
||||
|
||||
public ConfigureDataFixSchemaEvent(final Schema schema)
|
||||
{
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* The schema which you can configure.
|
||||
*
|
||||
* @return The schema
|
||||
*/
|
||||
public Schema getSchema()
|
||||
{
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of the schema that is about to be configured.
|
||||
*
|
||||
* @return The version.
|
||||
*/
|
||||
public int getVersion()
|
||||
{
|
||||
return getSchema().getVersionKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new simple (catch-all-nbt) block entity to the current schema and all its descendent versions.
|
||||
*
|
||||
* @param registryName The registry name of the block entity.
|
||||
*/
|
||||
public void registerSimpleBlockEntity(final ResourceLocation registryName)
|
||||
{
|
||||
registerBlockEntity(registryName, DSL::remainder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new simple (catch-all-nbt) block entity with an item inventory array on the nbt field with the given name to the current schema and all its descendent versions.
|
||||
*
|
||||
* @param registryName The registry name of the block entity.
|
||||
* @param nbtKey The name of the nbt field to where the item array is located.
|
||||
*/
|
||||
public void registerSimpleItemInventory(final ResourceLocation registryName, final String nbtKey)
|
||||
{
|
||||
registerBlockEntity(registryName, () -> DSL.optionalFields(nbtKey, DSL.list(References.ITEM_STACK.in(getSchema()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for the custom definition of a new block entity type and registration of it to the current schema and all its descendent versions.
|
||||
* The function is given the registry name of the block entity type template to produce.
|
||||
*
|
||||
* @param registryName The registry name of the block entity type.
|
||||
* @param template The function to produce the block entity type template.
|
||||
*/
|
||||
public void registerBlockEntity(final ResourceLocation registryName, final Function<String, TypeTemplate> template)
|
||||
{
|
||||
registerBlockEntity(registryName, () -> template.apply(registryName.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for the custom definition of a new block entity type and registration of it to the current schema and all its descendent versions.
|
||||
* The supplier is invoked to produce the block entity type template.
|
||||
*
|
||||
* @param registryName The registry name of the block entity type.
|
||||
* @param template The supplier of the type template.
|
||||
*/
|
||||
public void registerBlockEntity(final ResourceLocation registryName, final Supplier<TypeTemplate> template)
|
||||
{
|
||||
blockEntityTypes.put(registryName.toString(), template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an already registered block entity from the current schema and all its descendent versions.
|
||||
* This allows for the removal of a custom block entity type when it is registered in a parent version and is no longer needed.
|
||||
*
|
||||
* @param registryName The registry name of the block entity to remove.
|
||||
*/
|
||||
public void removeBlockEntity(final ResourceLocation registryName)
|
||||
{
|
||||
blockEntityTypes.put(registryName.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new simple (catch-all-nbt) entity to the current schema and all its descendent versions.
|
||||
*
|
||||
* @param registryName The registry name of the entity.
|
||||
*/
|
||||
public void registerSimpleEntity(final ResourceLocation registryName)
|
||||
{
|
||||
registerEntity(registryName, DSL::remainder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for the custom definition of a new entity type and registration of it to the current schema and all its descendent versions.
|
||||
* The function is given the registry name of the entity type template to produce.
|
||||
*
|
||||
* @param registryName The registry name of the entity type.
|
||||
* @param template The function to produce the entity type template.
|
||||
*/
|
||||
public void registerEntity(final ResourceLocation registryName, final Function<String, TypeTemplate> template)
|
||||
{
|
||||
registerEntity(registryName, () -> template.apply(registryName.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for the custom definition of a new entity type and registration of it to the current schema and all its descendent versions.
|
||||
* The supplier is invoked to produce the entity type template.
|
||||
*
|
||||
* @param registryName The registry name of the entity type.
|
||||
* @param template The supplier of the type template.
|
||||
*/
|
||||
public void registerEntity(final ResourceLocation registryName, final Supplier<TypeTemplate> template)
|
||||
{
|
||||
entityTypes.put(registryName.toString(), template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an already registered entity from the current schema and all its descendent versions.
|
||||
* This allows for the removal of a custom entity type when it is registered in a parent version and is no longer needed.
|
||||
*
|
||||
* @param registryName The registry name of the entity to remove.
|
||||
*/
|
||||
public void removeEntity(final ResourceLocation registryName)
|
||||
{
|
||||
entityTypes.put(registryName.toString(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* An unmodifiable map of all registered additional modded block entity types.
|
||||
* Keys may be mapped to a null value if they are supposed to be removed from the schema and its descendant versions, overriding the
|
||||
* type specification added in a parent version.
|
||||
*
|
||||
* @return The already registered additional modded block entity types.
|
||||
*/
|
||||
public Map<String, Supplier<TypeTemplate>> getEntityTypes()
|
||||
{
|
||||
return entityTypesView;
|
||||
}
|
||||
|
||||
/**
|
||||
* An unmodifiable map of all registered additional modded entity types.
|
||||
* Keys may be mapped to a null value if they are supposed to be removed from the schema and its descendant versions, overriding the
|
||||
* type specification added in a parent version.
|
||||
*
|
||||
* @return The already registered additional modded entity types.
|
||||
*/
|
||||
public Map<String, Supplier<TypeTemplate>> getBlockEntityTypes()
|
||||
{
|
||||
return blockEntityTypesView;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user