Implement a simple API for creating and registering FileFix-es#5257
Implement a simple API for creating and registering FileFix-es#5257eclipseisoffline wants to merge 4 commits intoFabricMC:26.1from
Conversation
|
I have tested the One thing that I have noted during my testing is that registering callbacks for the |
modmuss50
left a comment
There was a problem hiding this comment.
I think this is trying to do too much, at a glance this should just need to be a simple event. Please also add tests and update the data attachment api to use this.
| 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")); |
There was a problem hiding this comment.
We should not hard code vanilla's own operations here.
| import java.util.Map; | ||
| import java.util.function.Function; | ||
|
|
||
| public interface FileFixHelpers { |
| import java.util.Map; | ||
| import java.util.function.Function; | ||
|
|
||
| public interface FileFixHelpers { |
| import java.util.function.Function; | ||
|
|
||
| @FunctionalInterface | ||
| public interface FileFixSchemaRegisterCallback { |
There was a problem hiding this comment.
I have now written documentation here
|
|
||
| @SafeVarargs | ||
| static void registerFileFixes(int version, Function<Schema, FileFix>... fixes) { | ||
| EVENT.register((fileFixerUpper, schema, schemaVersion) -> { |
There was a problem hiding this comment.
What is the point in this function, is it not cleaner for a mod to just use the event?
There was a problem hiding this comment.
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.
| public interface FileFixHelpers { | ||
|
|
||
| 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")); | ||
| 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)); | ||
| } |
There was a problem hiding this comment.
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.
|
|
||
| @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;")) | ||
| 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); |
There was a problem hiding this comment.
The schema is passed to the FileFixSchemaRegisterCallback event. Mods will need the schema to create file fix instances.
| @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;")) |
There was a problem hiding this comment.
Will this not target every addSchema in the function? Why can it not just be a simple @Inject at the end of the function.
There was a problem hiding this comment.
It will, and that is intentional. Fixes added to a FileFixerUpper are added to the last schema registered at it. Placing an inject at the of the method would limit mods to only be able to add file fixes to the very last schema registered at the FileFixerUpper, which is not how it is intended to be used.
|
Apologies for the lack of documentation at first - I kind of forgot, whoops. I've now written documentation for the The idea with the Over all I am less sure on how good of an API this is. The core problem is that file fixes have to be registered before |
This PR adds a simple API for creating and registering file-fixes to the game's
FileFixerUpper.The main use case for this API, as of right now, is to easily allow mod developers using
SavedDatato support loading pre-Minecraft 26.1 worlds in 26.1 and above. Minecraft 26.1 has made huge changes to the way Minecraft worlds are structured directory wise, andSavedDataTypenow uses a namespaced ID compared to a simple string:This results in the following change in a world's directory layout:
Note the "or" in there:
SavedDatacan be used per-dimension, or globally/server-wide. In Minecraft 1.21.11 and below, server-wideSavedDatawas stored together with overworldSavedDatain the rootdatafolder. This was changed in Minecraft 26.1, overworldSavedDatais now stored in its respective folder in thedimensionsfolder.Vanilla Minecraft handles these path changes through
FileFixes, which are registered at aFileFixerUpperinstance (also see theDimensionStorageFileFixclass). This API allows mod developers to easily create and register theseFileFixes, so that they can provide support for loading worlds created in Minecraft 1.21.11 and below in 26.1.Currently, developers can register a
FileFixfor a specificSchemaversion as follows:(where
ExampleFileFixis aFileFixwithExampleFileFix(Schema)as constructor)This method allows multiple
FileFixconstructors to be passed at once. For more flexibility, developers can also use theFileFixSchemaRegisterCallback#EVENTdirectly. File-fixes added by mods are added to a schema before vanilla's file fixes.Developers can also make use of the utility methods present in the
FileFixHelpersinterface to create file-fixes orFileFixOperations. Currently, there are only methods to help aid the move to namespaced saved-data:createDimensionDataMoveOperationandcreateGlobalDataMoveOperation: can be used to create aFileFixOperationto move one or multipleSavedDatadata files to their respective folders for their new namespaced IDs.createDimensionDataMoveFileFixandcreateGlobalDataMoveFileFix: can be used to create aFileFixconstructor to move one or multipleSavedDatadata files to their respective folders for their new namespaced IDs.registerDimensionDataMoveFileFixandregisterGlobalDataMoveFileFix: can be used to create and automatically register aFileFixconstructor to move one or multipleSavedDatadata files to their respective folders for their new namespaced IDs. The file-fix is registered at schema 4772, in line with vanilla.These methods make it easy for mod developers to support loading older worlds in Minecraft 26.1, without losing
SavedData. This can be done as follows, for example: