diff --git a/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/ForgeCommonProvider.java b/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/ForgeCommonProvider.java index 57ef6a02..223afa45 100644 --- a/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/ForgeCommonProvider.java +++ b/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/ForgeCommonProvider.java @@ -9,6 +9,7 @@ import net.minecraft.world.level.storage.LevelResource; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.level.LevelEvent; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.bus.api.IEventBus; @@ -35,13 +36,13 @@ * @author pcal * @since 0.16.0 */ -class ForgeCommonProvider implements MinecraftProvider { +class ForgeCommonProvider implements MinecraftProvider, MixinGateway { static final String MOD_ID = "fastback"; private MinecraftServer logicalServer; private LifecycleListener lifecycleListener = null; private Runnable autoSaveListener; - private boolean isWorldSaveEnabled; + private boolean isWorldSaveEnabled = true; ForgeCommonProvider() { final IEventBus modEventBus = ModLoadingContext.get().getActiveContainer().getEventBus(); @@ -76,15 +77,6 @@ private void onRegisterCommandEvent(RegisterCommandsEvent event) { commandDispatcher.register(backupCommand); } - /** - TODO This one isn't it. We need to hear about it when an autosaves (and only autosaves) are completed. - Might have to delve into Forge mixins to do this. - private void onLevelSaveEvent(LevelEvent.Save event) { - provider.onAutoSaveComplete(); - } - **/ - - // ====================================================================== // Protected @@ -105,6 +97,7 @@ void onInitialize() { syslog().warn("Please note that this is an alpha release. A list of known issues is available here:"); syslog().warn("https://github.com/pcal43/fastback/issues?q=is%3Aissue+is%3Aopen+label%3Aforge"); syslog().warn("------------------------------------------------------------------------------------"); + MixinGateway.Singleton.register(this); } @@ -136,25 +129,32 @@ public String getModVersion() { return "0.15.3+1.20.1-alpha"; //FIXME } - //FIXME!! - void onAutoSaveComplete() { - syslog().debug("onAutoSaveComplete"); + @Override + public void autoSaveCompleted() { + syslog().debug("autoSaveCompleted"); this.autoSaveListener.run(); } @Override public Path getWorldDirectory() { - if (this.logicalServer == null) throw new IllegalStateException("minecraftServer is null"); + if (logicalServer == null) throw new IllegalStateException("minecraftServer is null"); return logicalServer.getWorldPath(LevelResource.ROOT).toAbsolutePath().normalize(); } @Override public void setWorldSaveEnabled(boolean enabled) { + isWorldSaveEnabled = enabled; + if (logicalServer == null) throw new IllegalStateException("minecraftServer is null"); for (ServerLevel world : logicalServer.getAllLevels()) { world.noSave = !enabled; } } + @Override + public boolean isWorldSaveEnabled() { + return isWorldSaveEnabled; + } + @Override public void saveWorld() { if (this.logicalServer == null) throw new IllegalStateException(); @@ -216,5 +216,4 @@ public Collection getModsBackupPaths() { **/ return out; } - } diff --git a/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/MixinGateway.java b/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/MixinGateway.java new file mode 100644 index 00000000..2929e0f7 --- /dev/null +++ b/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/MixinGateway.java @@ -0,0 +1,44 @@ +/* + * FastBack - Fast, incremental Minecraft backups powered by Git. + * Copyright (C) 2022 pcal.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ + +package net.pcal.fastback.mod.neoforge; + +/** + * Singleton 'gateway' that mixin code goes through to call back into the mod. + * + * @author pcal + * @since 0.13.1 + */ +public interface MixinGateway { + + static MixinGateway get() { + return Singleton.INSTANCE; + } + + boolean isWorldSaveEnabled(); + + void autoSaveCompleted(); + + class Singleton { + private static MixinGateway INSTANCE = null; + + public static void register(MixinGateway gateway) { + Singleton.INSTANCE = gateway; + } + } +} diff --git a/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/mixins/MinecraftServerMixin.java b/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/mixins/MinecraftServerMixin.java new file mode 100644 index 00000000..e1d6d83a --- /dev/null +++ b/neoforge/src/main/java/net/pcal/fastback/mod/neoforge/mixins/MinecraftServerMixin.java @@ -0,0 +1,84 @@ +/* + * FastBack - Fast, incremental Minecraft backups powered by Git. + * Copyright (C) 2022 pcal.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; If not, see . + */ +package net.pcal.fastback.mod.neoforge.mixins; + +import net.minecraft.server.MinecraftServer; +import net.pcal.fastback.mod.neoforge.MixinGateway; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import static net.pcal.fastback.logging.SystemLogger.syslog; + +/** + * Allows us to disable vanilla saving during 'git add' to avoid coherency problems in the backup snapshots. Also + * sends notifications when autosaving completes so we can follow them with automated backups. + * + * @author pcal + * @since 0.0.1 + */ +@Mixin(MinecraftServer.class) +public class MinecraftServerMixin { + + /** + * Intercept the call to saveAll that triggers on autosave, pass it through and then send out notification that + * the autosave is done. + */ + @Redirect(method = "autoSave()V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;saveEverything(ZZZ)Z")) + public boolean fastback_saveAll(MinecraftServer instance, boolean suppressLogs, boolean flush, boolean force) { + boolean result = instance.saveEverything(suppressLogs, flush, force); + MixinGateway.get().autoSaveCompleted(); + return result; + } + + /** + * Intercept save so we can hard-disable saving during critical parts of the backup. + */ + @Inject(at = @At("HEAD"), method = "saveAllChunks(ZZZ)Z", cancellable = true) + public void fastback_save(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable ci) { + synchronized (this) { + if (MixinGateway.get().isWorldSaveEnabled()) { + syslog().debug("world saves are enabled, doing requested save"); + } else { + syslog().warn("Skipping requested save because a backup is in progress."); + ci.setReturnValue(false); + ci.cancel(); + } + } + } + + /** + * Intercept saveAll so we can hard-disable saving during critical parts of the backup. + */ + @Inject(at = @At("HEAD"), method = "saveEverything(ZZZ)Z", cancellable = true) + public void fastback_saveAll(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable ci) { + synchronized (this) { + if (MixinGateway.get().isWorldSaveEnabled()) { + syslog().debug("world saves are enabled, doing requested saveAll"); + //TODO should call save here to ensure all synced? + } else { + syslog().warn("Skipping requested saveAll because a backup is in progress."); + ci.setReturnValue(false); + ci.cancel(); + } + } + } +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index a4d329af..24070ba1 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -27,3 +27,6 @@ mandatory = true versionRange = "1.21.3" ordering = "NONE" side = "CLIENT" + +[[mixins]] +config="fastback.mixins.json" diff --git a/neoforge/src/main/resources/fastback.mixins.json b/neoforge/src/main/resources/fastback.mixins.json new file mode 100644 index 00000000..cc2278b9 --- /dev/null +++ b/neoforge/src/main/resources/fastback.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.pcal.fastback.mod.neoforge.mixins", + "compatibilityLevel": "JAVA_16", + "mixins": [ + "MinecraftServerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +}