Skip to content

Commit 94433b0

Browse files
committed
Merge 1.18 into 1.19.2
2 parents ab7c982 + 8ac43d5 commit 94433b0

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package org.embeddedt.modernfix.forge.config;
2+
3+
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
4+
import com.electronwill.nightconfig.core.file.FileConfig;
5+
import com.electronwill.nightconfig.core.file.FileWatcher;
6+
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
7+
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
8+
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
9+
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
10+
import org.embeddedt.modernfix.util.CommonModUtil;
11+
12+
import java.lang.reflect.Field;
13+
import java.nio.file.Path;
14+
import java.util.Collections;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
import java.util.Set;
18+
import java.util.concurrent.ConcurrentHashMap;
19+
import java.util.function.Function;
20+
21+
/**
22+
* Relatively simple patch to wait for config saving to finish, made complex by Night Config classes being package-private,
23+
* and Forge not allowing mixins into libraries.
24+
*/
25+
public class NightConfigFixer {
26+
public static void monitorFileWatcher() {
27+
CommonModUtil.runWithoutCrash(() -> {
28+
FileWatcher watcher = FileWatcher.defaultInstance();
29+
Field field = FileWatcher.class.getDeclaredField("watchedFiles");
30+
field.setAccessible(true);
31+
ConcurrentHashMap<Path, ?> theMap = (ConcurrentHashMap<Path, ?>)field.get(watcher);
32+
// replace the backing map of watched files with one we control
33+
field.set(watcher, new MonitoringMap(theMap));
34+
ModernFixMixinPlugin.instance.logger.info("Applied Forge config corruption patch");
35+
}, "replacing Night Config watchedFiles map");
36+
}
37+
38+
private static final Class<?> WATCHED_FILE = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.FileWatcher$WatchedFile"));
39+
private static final Field CHANGE_HANDLER = ObfuscationReflectionHelper.findField(WATCHED_FILE, "changeHandler");
40+
41+
private static final Class<?> WRITE_SYNC_CONFIG = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.WriteSyncFileConfig"));
42+
private static final Class<?> AUTOSAVE_CONFIG = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.AutosaveCommentedFileConfig"));
43+
private static final Field AUTOSAVE_FILECONFIG = ObfuscationReflectionHelper.findField(AUTOSAVE_CONFIG, "fileConfig");
44+
45+
private static final Field CURRENTLY_WRITING = ObfuscationReflectionHelper.findField(WRITE_SYNC_CONFIG, "currentlyWriting");
46+
47+
private static final Map<Class<?>, Field> CONFIG_WATCHER_TO_CONFIG_FIELD = Collections.synchronizedMap(new HashMap<>());
48+
49+
private static Field getCurrentlyWritingFieldOnWatcher(Object watcher) {
50+
return CONFIG_WATCHER_TO_CONFIG_FIELD.computeIfAbsent(watcher.getClass(), clz -> {
51+
while(clz != null && clz != Object.class) {
52+
for(Field f : clz.getDeclaredFields()) {
53+
if(CommentedFileConfig.class.isAssignableFrom(f.getType())) {
54+
f.setAccessible(true);
55+
ModernFixMixinPlugin.instance.logger.debug("Found CommentedFileConfig: field '{}' on {}", f.getName(), clz.getName());
56+
return f;
57+
}
58+
}
59+
clz = clz.getSuperclass();
60+
}
61+
return null;
62+
});
63+
}
64+
65+
static class MonitoringMap extends ConcurrentHashMap<Path, Object> {
66+
public MonitoringMap(ConcurrentHashMap<Path, ?> oldMap) {
67+
super(oldMap);
68+
}
69+
70+
@Override
71+
public Object computeIfAbsent(Path key, Function<? super Path, ?> mappingFunction) {
72+
return super.computeIfAbsent(key, path -> {
73+
Object watchedFile = mappingFunction.apply(path);
74+
try {
75+
Runnable changeHandler = (Runnable)CHANGE_HANDLER.get(watchedFile);
76+
CHANGE_HANDLER.set(watchedFile, new MonitoringConfigTracker(changeHandler));
77+
} catch(ReflectiveOperationException e) {
78+
e.printStackTrace();
79+
}
80+
return watchedFile;
81+
});
82+
}
83+
}
84+
85+
static class MonitoringConfigTracker implements Runnable {
86+
private final Runnable configTracker;
87+
88+
MonitoringConfigTracker(Runnable r) {
89+
this.configTracker = r;
90+
}
91+
92+
private static final Set<Class<?>> UNKNOWN_FILE_CONFIG_CLASSES = Collections.synchronizedSet(new ReferenceOpenHashSet<>());
93+
94+
private boolean isSaving(FileConfig config) throws ReflectiveOperationException {
95+
if(WRITE_SYNC_CONFIG.isAssignableFrom(config.getClass())) {
96+
return CURRENTLY_WRITING.getBoolean(config);
97+
} else if(AUTOSAVE_CONFIG.isAssignableFrom(config.getClass())) {
98+
FileConfig fc = (FileConfig)AUTOSAVE_FILECONFIG.get(config);
99+
return isSaving(fc);
100+
} else {
101+
if(UNKNOWN_FILE_CONFIG_CLASSES.add(config.getClass()))
102+
ModernFixMixinPlugin.instance.logger.warn("Unexpected FileConfig class: {}", config.getClass().getName());
103+
return false;
104+
}
105+
}
106+
107+
/**
108+
* This entrypoint runs when the file watcher has detected a change on the config file. Before passing
109+
* this through to Forge, use reflection hacks to confirm the config system is not still writing to the file.
110+
* If it is, spin until writing finishes. Immediately returning might result in the event never being observed.
111+
*/
112+
@Override
113+
public void run() {
114+
try {
115+
Field theField = getCurrentlyWritingFieldOnWatcher(this.configTracker);
116+
if(theField != null) {
117+
CommentedFileConfig cfg = (CommentedFileConfig)theField.get(this.configTracker);
118+
while(isSaving(cfg)) {
119+
// block until the config is no longer marked as saving
120+
// Forge shouldn't save the config twice in a row under normal conditions so this should fix the
121+
// original bug
122+
try {
123+
Thread.sleep(500);
124+
} catch (InterruptedException e) {
125+
return;
126+
}
127+
}
128+
}
129+
} catch(ReflectiveOperationException e) {
130+
e.printStackTrace();
131+
}
132+
configTracker.run();
133+
}
134+
}
135+
}

forge/src/main/java/org/embeddedt/modernfix/platform/forge/ModernFixPlatformHooksImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
2626
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
2727
import org.embeddedt.modernfix.forge.classloading.FastAccessTransformerList;
28+
import org.embeddedt.modernfix.forge.config.NightConfigFixer;
2829
import org.embeddedt.modernfix.forge.packet.PacketHandler;
2930
import org.embeddedt.modernfix.spark.SparkLaunchProfiler;
3031
import org.embeddedt.modernfix.util.CommonModUtil;
@@ -144,6 +145,8 @@ public static void injectPlatformSpecificHacks() {
144145
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnForge")) {
145146
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler");
146147
}
148+
149+
NightConfigFixer.monitorFileWatcher();
147150
}
148151

149152
public static void applyASMTransformers(String mixinClassName, ClassNode targetClass) {

0 commit comments

Comments
 (0)