Skip to content

Commit cd6cbc2

Browse files
committed
Resolve NeoForge equivilent of '#261 autoback (backup after autosaves) is not currently supported on Forge'
Mixins are used in much the same way they are in the fabric version.
1 parent 851213a commit cd6cbc2

File tree

5 files changed

+158
-16
lines changed

5 files changed

+158
-16
lines changed

neoforge/src/main/java/net/pcal/fastback/mod/neoforge/ForgeCommonProvider.java

+15-16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import net.minecraft.world.level.storage.LevelResource;
1010
import net.neoforged.neoforge.common.NeoForge;
1111
import net.neoforged.neoforge.event.RegisterCommandsEvent;
12+
import net.neoforged.neoforge.event.level.LevelEvent;
1213
import net.neoforged.neoforge.event.server.ServerStartedEvent;
1314
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
1415
import net.neoforged.bus.api.IEventBus;
@@ -35,13 +36,13 @@
3536
* @author pcal
3637
* @since 0.16.0
3738
*/
38-
class ForgeCommonProvider implements MinecraftProvider {
39+
class ForgeCommonProvider implements MinecraftProvider, MixinGateway {
3940

4041
static final String MOD_ID = "fastback";
4142
private MinecraftServer logicalServer;
4243
private LifecycleListener lifecycleListener = null;
4344
private Runnable autoSaveListener;
44-
private boolean isWorldSaveEnabled;
45+
private boolean isWorldSaveEnabled = true;
4546

4647
ForgeCommonProvider() {
4748
final IEventBus modEventBus = ModLoadingContext.get().getActiveContainer().getEventBus();
@@ -76,15 +77,6 @@ private void onRegisterCommandEvent(RegisterCommandsEvent event) {
7677
commandDispatcher.register(backupCommand);
7778
}
7879

79-
/**
80-
TODO This one isn't it. We need to hear about it when an autosaves (and only autosaves) are completed.
81-
Might have to delve into Forge mixins to do this.
82-
private void onLevelSaveEvent(LevelEvent.Save event) {
83-
provider.onAutoSaveComplete();
84-
}
85-
**/
86-
87-
8880
// ======================================================================
8981
// Protected
9082

@@ -104,6 +96,7 @@ void onInitialize() {
10496
syslog().warn("Please note that this is an alpha release. A list of known issues is available here:");
10597
syslog().warn("https://github.com/pcal43/fastback/issues?q=is%3Aissue+is%3Aopen+label%3Aforge");
10698
syslog().warn("------------------------------------------------------------------------------------");
99+
MixinGateway.Singleton.register(this);
107100
}
108101

109102

@@ -135,25 +128,32 @@ public String getModVersion() {
135128
return "0.15.3+1.20.1-alpha"; //FIXME
136129
}
137130

138-
//FIXME!!
139-
void onAutoSaveComplete() {
140-
syslog().debug("onAutoSaveComplete");
131+
@Override
132+
public void autoSaveCompleted() {
133+
syslog().debug("autoSaveCompleted");
141134
this.autoSaveListener.run();
142135
}
143136

144137
@Override
145138
public Path getWorldDirectory() {
146-
if (this.logicalServer == null) throw new IllegalStateException("minecraftServer is null");
139+
if (logicalServer == null) throw new IllegalStateException("minecraftServer is null");
147140
return logicalServer.getWorldPath(LevelResource.ROOT).toAbsolutePath().normalize();
148141
}
149142

150143
@Override
151144
public void setWorldSaveEnabled(boolean enabled) {
145+
isWorldSaveEnabled = enabled;
146+
if (logicalServer == null) throw new IllegalStateException("minecraftServer is null");
152147
for (ServerLevel world : logicalServer.getAllLevels()) {
153148
world.noSave = !enabled;
154149
}
155150
}
156151

152+
@Override
153+
public boolean isWorldSaveEnabled() {
154+
return isWorldSaveEnabled;
155+
}
156+
157157
@Override
158158
public void saveWorld() {
159159
if (this.logicalServer == null) throw new IllegalStateException();
@@ -215,5 +215,4 @@ public Collection<Path> getModsBackupPaths() {
215215
**/
216216
return out;
217217
}
218-
219218
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* FastBack - Fast, incremental Minecraft backups powered by Git.
3+
* Copyright (C) 2022 pcal.net
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
package net.pcal.fastback.mod.neoforge;
20+
21+
/**
22+
* Singleton 'gateway' that mixin code goes through to call back into the mod.
23+
*
24+
* @author pcal
25+
* @since 0.13.1
26+
*/
27+
public interface MixinGateway {
28+
29+
static MixinGateway get() {
30+
return Singleton.INSTANCE;
31+
}
32+
33+
boolean isWorldSaveEnabled();
34+
35+
void autoSaveCompleted();
36+
37+
class Singleton {
38+
private static MixinGateway INSTANCE = null;
39+
40+
public static void register(MixinGateway gateway) {
41+
Singleton.INSTANCE = gateway;
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* FastBack - Fast, incremental Minecraft backups powered by Git.
3+
* Copyright (C) 2022 pcal.net
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.pcal.fastback.mod.neoforge.mixins;
19+
20+
import net.minecraft.server.MinecraftServer;
21+
import net.pcal.fastback.mod.neoforge.MixinGateway;
22+
import org.spongepowered.asm.mixin.Mixin;
23+
import org.spongepowered.asm.mixin.injection.At;
24+
import org.spongepowered.asm.mixin.injection.Inject;
25+
import org.spongepowered.asm.mixin.injection.Redirect;
26+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
27+
28+
import static net.pcal.fastback.logging.SystemLogger.syslog;
29+
30+
/**
31+
* Allows us to disable vanilla saving during 'git add' to avoid coherency problems in the backup snapshots. Also
32+
* sends notifications when autosaving completes so we can follow them with automated backups.
33+
*
34+
* @author pcal
35+
* @since 0.0.1
36+
*/
37+
@Mixin(MinecraftServer.class)
38+
public class MinecraftServerMixin {
39+
40+
/**
41+
* Intercept the call to saveAll that triggers on autosave, pass it through and then send out notification that
42+
* the autosave is done.
43+
*/
44+
@Redirect(method = "autoSave()V",
45+
at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;saveEverything(ZZZ)Z"))
46+
public boolean fastback_saveAll(MinecraftServer instance, boolean suppressLogs, boolean flush, boolean force) {
47+
boolean result = instance.saveEverything(suppressLogs, flush, force);
48+
MixinGateway.get().autoSaveCompleted();
49+
return result;
50+
}
51+
52+
/**
53+
* Intercept save so we can hard-disable saving during critical parts of the backup.
54+
*/
55+
@Inject(at = @At("HEAD"), method = "saveAllChunks(ZZZ)Z", cancellable = true)
56+
public void fastback_save(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable<Boolean> ci) {
57+
synchronized (this) {
58+
if (MixinGateway.get().isWorldSaveEnabled()) {
59+
syslog().debug("world saves are enabled, doing requested save");
60+
} else {
61+
syslog().warn("Skipping requested save because a backup is in progress.");
62+
ci.setReturnValue(false);
63+
ci.cancel();
64+
}
65+
}
66+
}
67+
68+
/**
69+
* Intercept saveAll so we can hard-disable saving during critical parts of the backup.
70+
*/
71+
@Inject(at = @At("HEAD"), method = "saveEverything(ZZZ)Z", cancellable = true)
72+
public void fastback_saveAll(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable<Boolean> ci) {
73+
synchronized (this) {
74+
if (MixinGateway.get().isWorldSaveEnabled()) {
75+
syslog().debug("world saves are enabled, doing requested saveAll");
76+
//TODO should call save here to ensure all synced?
77+
} else {
78+
syslog().warn("Skipping requested saveAll because a backup is in progress.");
79+
ci.setReturnValue(false);
80+
ci.cancel();
81+
}
82+
}
83+
}
84+
}

neoforge/src/main/resources/META-INF/neoforge.mods.toml

+3
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ mandatory = true
2727
versionRange = "1.21.3"
2828
ordering = "NONE"
2929
side = "CLIENT"
30+
31+
[[mixins]]
32+
config="fastback.mixins.json"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"required": true,
3+
"minVersion": "0.8",
4+
"package": "net.pcal.fastback.mod.neoforge.mixins",
5+
"compatibilityLevel": "JAVA_16",
6+
"mixins": [
7+
"MinecraftServerMixin"
8+
],
9+
"injectors": {
10+
"defaultRequire": 1
11+
}
12+
}

0 commit comments

Comments
 (0)