-
Notifications
You must be signed in to change notification settings - Fork 521
Implement a simple API for creating and registering FileFix-es #5257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 26.1
Are you sure you want to change the base?
Changes from all commits
927efa7
3bf94ca
f454f0d
4d443b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package net.fabricmc.fabric.api.serialization.v1.filefix; | ||
|
|
||
| import com.mojang.datafixers.schemas.Schema; | ||
|
|
||
| import net.fabricmc.fabric.impl.serialization.filefix.CombinedFileFixOperation; | ||
| import net.fabricmc.fabric.impl.serialization.filefix.FileFixHelpersImpl; | ||
|
|
||
| import net.fabricmc.fabric.mixin.serialization.filefix.FileFixerUpperAccessor; | ||
|
|
||
| import net.minecraft.resources.Identifier; | ||
| import net.minecraft.util.filefix.FileFix; | ||
| import net.minecraft.util.filefix.access.FileRelation; | ||
| import net.minecraft.util.filefix.operations.FileFixOperation; | ||
| import net.minecraft.util.filefix.operations.FileFixOperations; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
|
|
||
| public interface FileFixHelpers { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No docs |
||
|
|
||
| static FileFixOperation createDimensionDataMoveOperation(String oldSaveId, Identifier newSaveId) { | ||
| return createDimensionDataMoveOperation(Map.of(oldSaveId, newSaveId)); | ||
| } | ||
|
|
||
| static FileFixOperation createDimensionDataMoveOperation(Map<String, Identifier> saveIdMap) { | ||
| List<FileFixOperation> operations = new ArrayList<>(); | ||
| operations.addAll(FileFixHelpersImpl.createDimensionMoveOperations(saveIdMap, "", "dimensions/minecraft/overworld")); | ||
| operations.addAll(FileFixHelpersImpl.createDimensionMoveOperations(saveIdMap, "DIM-1", "dimensions/minecraft/the_nether")); | ||
| operations.addAll(FileFixHelpersImpl.createDimensionMoveOperations(saveIdMap, "DIM1", "dimensions/minecraft/the_end")); | ||
|
Comment on lines
+29
to
+31
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should not hard code vanilla's own operations here. |
||
| operations.add(FileFixHelpersImpl.createCustomDimensionDataMoveOperation(saveIdMap)); | ||
| return new CombinedFileFixOperation(operations); | ||
| } | ||
|
|
||
| static Function<Schema, FileFix> createDimensionDataMoveFileFix(String oldSaveId, Identifier newSaveId) { | ||
| return createDimensionDataMoveFileFix(Map.of(oldSaveId, newSaveId)); | ||
| } | ||
|
|
||
| static Function<Schema, FileFix> createDimensionDataMoveFileFix(Map<String, Identifier> saveIdMap) { | ||
| return schema -> new FileFix(schema) { | ||
| @Override | ||
| public void makeFixer() { | ||
| addFileFixOperation(createDimensionDataMoveOperation(saveIdMap)); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| static void registerDimensionDataMoveFileFix(String oldSaveId, Identifier newSaveId) { | ||
| registerDimensionDataMoveFileFix(Map.of(oldSaveId, newSaveId)); | ||
| } | ||
|
|
||
| static void registerDimensionDataMoveFileFix(Map<String, Identifier> saveIdMap) { | ||
| FileFixSchemaRegisterCallback.registerFileFixes(FileFixerUpperAccessor.getFileFixerIntroductionVersion(), createDimensionDataMoveFileFix(saveIdMap)); | ||
| } | ||
|
|
||
| static FileFixOperation createGlobalDataMoveOperation(String oldSaveId, Identifier newSaveId) { | ||
| return createGlobalDataMoveOperation(Map.of(oldSaveId, newSaveId)); | ||
| } | ||
|
|
||
| static FileFixOperation createGlobalDataMoveOperation(Map<String, Identifier> saveIdMap) { | ||
| return FileFixOperations.applyInFolders( | ||
| FileRelation.DATA, | ||
| saveIdMap.entrySet().stream() | ||
| .map(entry -> FileFixHelpersImpl.createNamespacedDataMoveOperation(entry.getKey(), entry.getValue(), "", "")) | ||
| .toList() | ||
| ); | ||
| } | ||
|
|
||
| static Function<Schema, FileFix> createGlobalDataMoveFileFix(String oldSaveId, Identifier newSaveId) { | ||
| return createGlobalDataMoveFileFix(Map.of(oldSaveId, newSaveId)); | ||
| } | ||
|
|
||
| static Function<Schema, FileFix> createGlobalDataMoveFileFix(Map<String, Identifier> saveIdMap) { | ||
| return schema -> new FileFix(schema) { | ||
| @Override | ||
| public void makeFixer() { | ||
| addFileFixOperation(createGlobalDataMoveOperation(saveIdMap)); | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| static void registerGlobalDataMoveFileFix(String oldSaveId, Identifier newSaveId) { | ||
| registerGlobalDataMoveFileFix(Map.of(oldSaveId, newSaveId)); | ||
| } | ||
|
|
||
| static void registerGlobalDataMoveFileFix(Map<String, Identifier> saveIdMap) { | ||
| FileFixSchemaRegisterCallback.registerFileFixes(FileFixerUpperAccessor.getFileFixerIntroductionVersion(), createGlobalDataMoveFileFix(saveIdMap)); | ||
| } | ||
|
Comment on lines
+21
to
+89
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd argue most of this file is out of scope, a mod can easily create its own file fix as required. Lets just keep the api simple. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package net.fabricmc.fabric.api.serialization.v1.filefix; | ||
|
|
||
| import com.mojang.datafixers.schemas.Schema; | ||
|
|
||
| import net.fabricmc.fabric.api.event.Event; | ||
| import net.fabricmc.fabric.api.event.EventFactory; | ||
|
|
||
| import net.minecraft.util.filefix.FileFix; | ||
| import net.minecraft.util.filefix.FileFixerUpper; | ||
|
|
||
| import java.util.function.Function; | ||
|
|
||
| /** | ||
| * Event for registering file fixes. This event is called for every {@link Schema} added to | ||
| * Minecraft's {@link FileFixerUpper}, before any of vanilla's fixes are added to it. | ||
| * | ||
| * <p>Please note that this event is called early during the game launch, before {@link net.fabricmc.api.ModInitializer}s | ||
| * are run. Therefore, make sure to register callbacks early during the mod loading process, using | ||
| * a {@link net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint}.</p> | ||
| */ | ||
| @FunctionalInterface | ||
| public interface FileFixSchemaRegisterCallback { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No docs
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have now written documentation here |
||
|
|
||
| Event<FileFixSchemaRegisterCallback> EVENT = EventFactory.createArrayBacked(FileFixSchemaRegisterCallback.class, | ||
| listeners -> (fileFixerUpper, schema, version) -> { | ||
| for (FileFixSchemaRegisterCallback callback : listeners) { | ||
| callback.schemaRegistered(fileFixerUpper, schema, version); | ||
| } | ||
| }); | ||
|
|
||
| void schemaRegistered(FileFixerUpper.Builder fileFixerUpper, Schema schema, int version); | ||
|
|
||
| /** | ||
| * Can be used to easily register file fixes for a data version. This method registers a callback | ||
| * at {@link FileFixSchemaRegisterCallback#EVENT}, which adds the given {@code fixes} to the | ||
| * game's {@link FileFixerUpper} at the given data version. | ||
| * | ||
| * <p>Please note that this method does not throw for invalid data versions, and that | ||
| * fixes can only be registered for a data version that has file fixes in vanilla, because | ||
| * there won't be a schema registered for those versions.</p> | ||
| * | ||
| * @param version the data version to register the file fixes for. | ||
| * @param fixes the file fixes to register. | ||
| */ | ||
| @SafeVarargs | ||
| static void registerFileFixes(int version, Function<Schema, FileFix>... fixes) { | ||
| EVENT.register((fileFixerUpper, schema, schemaVersion) -> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the point in this function, is it not cleaner for a mod to just use the event?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mods will usually want to register file fixes for only one data version at a time. This method functions as a shortcut for that. In my opinion, it is cleaner to write: FileFixSchemaRegisterCallback.registerFileFixes(4772, FileFixA::new, FileFixB::new);
FileFixSchemaRegisterCallback.registerFileFixes(4773, FileFixC::new);Rather than: FileFixSchemaRegisterCallback.EVENT.register((fileFixerUpper, schema, version) -> {
if (version == 4772) {
fileFixerUpper.addFixer(new FileFixA(schema));
fileFixerUpper.addFixer(new FileFixB(schema));
} else if (version == 4773) {
fileFixerUpper.addFixer(new FileFixC(schema));
}
});Although I do suppose the latter would lead to less callbacks being registered at the event. |
||
| if (schemaVersion == version) { | ||
| for (Function<Schema, FileFix> fix : fixes) { | ||
| fileFixerUpper.addFixer(fix.apply(schema)); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package net.fabricmc.fabric.impl.serialization.filefix; | ||
|
|
||
| import net.minecraft.util.filefix.operations.FileFixOperation; | ||
| import net.minecraft.util.worldupdate.UpgradeProgress; | ||
|
|
||
| import java.io.IOException; | ||
| import java.nio.file.Path; | ||
| import java.util.List; | ||
|
|
||
| public record CombinedFileFixOperation(List<FileFixOperation> operations) implements FileFixOperation { | ||
|
|
||
| @Override | ||
| public void fix(Path baseDirectory, UpgradeProgress upgradeProgress) throws IOException { | ||
| for (FileFixOperation operation : operations) { | ||
| operation.fix(baseDirectory, upgradeProgress); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package net.fabricmc.fabric.impl.serialization.filefix; | ||
|
|
||
| import net.minecraft.resources.Identifier; | ||
| import net.minecraft.util.filefix.access.FileRelation; | ||
| import net.minecraft.util.filefix.operations.FileFixOperation; | ||
| import net.minecraft.util.filefix.operations.FileFixOperations; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public interface FileFixHelpersImpl { | ||
|
|
||
| static List<FileFixOperation> createDimensionMoveOperations(Map<String, Identifier> saveIdMap, | ||
| String oldDimensionPath, | ||
| String newDimensionPath) { | ||
| return saveIdMap.entrySet().stream() | ||
| .map(entry -> createNamespacedDataMoveOperation(entry.getKey(), entry.getValue(), | ||
| oldDimensionPath + "/data/", newDimensionPath + "/data/")) | ||
| .toList(); | ||
| } | ||
|
|
||
| static FileFixOperation createCustomDimensionDataMoveOperation(Map<String, Identifier> saveIdMap) { | ||
| return FileFixOperations.applyInFolders( | ||
| FileRelation.DIMENSIONS_DATA, | ||
| saveIdMap.entrySet().stream() | ||
| .map(entry -> createNamespacedDataMoveOperation(entry.getKey(), entry.getValue(), "", "")) | ||
| .toList() | ||
| ); | ||
| } | ||
|
|
||
| static FileFixOperation createNamespacedDataMoveOperation(String oldId, Identifier newId, String oldPrefix, String newPrefix) { | ||
| return FileFixOperations.move(oldPrefix + oldId + ".dat", newPrefix + newId.getNamespace() + "/" + newId.getPath() + ".dat"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package net.fabricmc.fabric.mixin.serialization.filefix; | ||
|
|
||
| import com.llamalad7.mixinextras.injector.wrapoperation.Operation; | ||
| import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; | ||
| import com.mojang.datafixers.DataFixerBuilder; | ||
| import com.mojang.datafixers.schemas.Schema; | ||
|
|
||
| import net.fabricmc.fabric.api.serialization.v1.filefix.FileFixSchemaRegisterCallback; | ||
|
|
||
| import net.minecraft.util.datafix.DataFixers; | ||
|
|
||
| import net.minecraft.util.filefix.FileFixerUpper; | ||
|
|
||
| import org.spongepowered.asm.mixin.Mixin; | ||
| import org.spongepowered.asm.mixin.injection.At; | ||
|
|
||
| import java.util.function.BiFunction; | ||
|
|
||
| @Mixin(DataFixers.class) | ||
| public abstract class DataFixersMixin { | ||
|
|
||
| @WrapOperation(method = "addFixers", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/filefix/FileFixerUpper$Builder;addSchema(Lcom/mojang/datafixers/DataFixerBuilder;ILjava/util/function/BiFunction;)Lcom/mojang/datafixers/schemas/Schema;")) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this not target every addSchema in the function? Why can it not just be a simple @Inject at the end of the function.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will, and that is intentional. Fixes added to a |
||
| private static Schema runSchemaRegisterCallback(FileFixerUpper.Builder instance, DataFixerBuilder fixerUpper, int version, BiFunction<Integer, Schema, Schema> factory, Operation<Schema> original) { | ||
| Schema schema = original.call(instance, fixerUpper, version, factory); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whats the schema used for?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The schema is passed to the |
||
| FileFixSchemaRegisterCallback.EVENT.invoker().schemaRegistered(instance, schema, version); | ||
| return schema; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package net.fabricmc.fabric.mixin.serialization.filefix; | ||
|
|
||
| import net.minecraft.util.filefix.FileFixerUpper; | ||
|
|
||
| import org.spongepowered.asm.mixin.Mixin; | ||
| import org.spongepowered.asm.mixin.gen.Accessor; | ||
|
|
||
| @Mixin(FileFixerUpper.class) | ||
| public interface FileFixerUpperAccessor { | ||
|
|
||
| @Accessor("FILE_FIXER_INTRODUCTION_VERSION") | ||
| static int getFileFixerIntroductionVersion() { | ||
| throw new AssertionError(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| @NullMarked | ||
| package net.fabricmc.fabric.mixin.serialization.filefix; | ||
|
|
||
| import org.jspecify.annotations.NullMarked; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this an interface