feat(config-core): Configurable config watch service

This commit is contained in:
Johannes Frohnmeyer 2023-07-09 13:42:08 +02:00
parent a1a6d22510
commit e2a891c2e2
Signed by: Johannes
GPG Key ID: E76429612C2929F4
14 changed files with 126 additions and 33 deletions

View File

@ -3,7 +3,7 @@ package io.gitlab.jfronny.libjf.config.plugin.fmj;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import io.gitlab.jfronny.libjf.config.plugin.asm.ConfigInjectClassTransformer;
import org.objectweb.asm.Type;
@ -14,7 +14,7 @@ public class FabricModJsonTransformer {
private static final Gson INPUT_GSON = new GsonBuilder().setLenient().create();
private static final Gson OUTPUT_GSON = new GsonBuilder().create();
private static final String ENTRYPOINTS = "entrypoints";
private static final String LIBJF_CONFIG = ConfigHolderImpl.MODULE_ID;
private static final String LIBJF_CONFIG = ConfigCore.MODULE_ID;
public static void transform(JsonReader reader, JsonWriter writer, Set<Type> configClasses) {
JsonObject fmj = INPUT_GSON.<JsonElement>fromJson(reader, JsonElement.class).getAsJsonObject();
if (!fmj.has(ENTRYPOINTS)) fmj.add(ENTRYPOINTS, new JsonObject());

View File

@ -5,11 +5,17 @@ import com.terraformersmc.modmenu.api.ModMenuApi;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import java.util.HashMap;
import java.util.Map;
public class ModMenuAdapter implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return buildFactory(ConfigCore.CONFIG_INSTANCE);
}
@Override
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
Map<String, ConfigScreenFactory<?>> factories = new HashMap<>();

View File

@ -12,7 +12,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.LinkedList;
import java.util.List;
import static io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl.MODULE_ID;
import static io.gitlab.jfronny.libjf.config.impl.ConfigCore.MODULE_ID;
public class AuxiliaryMetadata {
public static AuxiliaryMetadata of(Category category) {

View File

@ -0,0 +1,17 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
public class ConfigCore {
public static final String MOD_ID = "libjf-config-core-v1";
public static final String MODULE_ID = "libjf:config";
public static boolean watchForChanges = true;
public static final ConfigInstance CONFIG_INSTANCE;
static {
CONFIG_INSTANCE = DSL.create(MOD_ID).register(builder -> builder
.value("watchForChanges", watchForChanges, () -> watchForChanges, b -> watchForChanges = b)
);
}
}

View File

@ -18,7 +18,7 @@ public class ConfigHolderImpl implements ConfigHolder {
public static final ConfigHolderImpl INSTANCE = new ConfigHolderImpl();
private ConfigHolderImpl() {
}
public static final String MODULE_ID = "libjf:config";
private final Map<String, ConfigInstance> configs = new HashMap<>();
private final Map<Path, ConfigInstance> configsByPath = new HashMap<>();

View File

@ -1,16 +1,22 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.JsonObject;
import io.gitlab.jfronny.gson.JsonParser;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v1.*;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigCategory;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.v1.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
import io.gitlab.jfronny.libjf.config.impl.JfConfigWatchService;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import io.gitlab.jfronny.libjf.config.impl.watch.JfConfigWatchService;
import net.fabricmc.loader.api.FabricLoader;
import java.io.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

View File

@ -3,7 +3,7 @@ package io.gitlab.jfronny.libjf.config.impl.entrypoint;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v1.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
@ -19,10 +19,10 @@ public class JfConfigSafe implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
LibJf.setup();
for (EntrypointContainer<Object> custom : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, Object.class)) {
if (!REGISTERED_MODS.contains(custom.getProvider().getMetadata().getId()) && custom.getEntrypoint() instanceof JfCustomConfig cfg) {
REGISTERED_MODS.add(custom.getProvider().getMetadata().getId());
cfg.register(DSL.create(custom.getProvider().getMetadata().getId()));
for (EntrypointContainer<Object> custom : FabricLoader.getInstance().getEntrypointContainers(ConfigCore.MODULE_ID, Object.class)) {
String id = custom.getProvider().getMetadata().getId();
if (custom.getEntrypoint() instanceof JfCustomConfig cfg && REGISTERED_MODS.add(id)) {
cfg.register(DSL.create(id));
}
}
TRANSLATION_SUPPLIER = s -> {

View File

@ -0,0 +1,18 @@
package io.gitlab.jfronny.libjf.config.impl.watch;
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
import io.gitlab.jfronny.libjf.LibJf;
import java.io.Closeable;
import java.nio.file.Path;
public interface JfConfigWatchService extends Closeable {
static <TEx extends Throwable> void lock(Path p, ThrowingRunnable<TEx> task) throws TEx {
JfConfigWatchServiceImpl.lock(p, task
.compose(() -> LibJf.LOGGER.info("Locking"))
.andThen(() -> LibJf.LOGGER.info("Ran task after lock"))
);
}
void executeIteration();
}

View File

@ -1,34 +1,35 @@
package io.gitlab.jfronny.libjf.config.impl;
package io.gitlab.jfronny.libjf.config.impl.watch;
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v1.ConfigHolder;
import io.gitlab.jfronny.libjf.coprocess.ThreadCoProcess;
import net.fabricmc.loader.api.FabricLoader;
import java.io.Closeable;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static java.nio.file.StandardWatchEventKinds.*;
public class JfConfigWatchService extends ThreadCoProcess implements Closeable {
public class JfConfigWatchServiceImpl implements JfConfigWatchService {
private static final Path CONFIG_DIR = FabricLoader.getInstance().getConfigDir();
private static final Set<JfConfigWatchService> REGISTERED_INSTANCES = new HashSet<>();
private static final Set<JfConfigWatchServiceImpl> REGISTERED_INSTANCES = new HashSet<>();
private final WatchService service;
private static final Map<Path, Integer> locked = new HashMap<>();
public static <TEx extends Throwable> void lock(Path p, ThrowingRunnable<TEx> task) throws TEx {
synchronized (CONFIG_DIR) {
locked.compute(p, (p1, val) -> val == null ? 1 : val + 1);
task.run();
for (JfConfigWatchService instance : REGISTERED_INSTANCES) {
for (JfConfigWatchServiceImpl instance : REGISTERED_INSTANCES) {
instance.executeIteration();
}
}
}
public JfConfigWatchService() {
public JfConfigWatchServiceImpl() {
WatchService ws = null;
try {
ws = FileSystems.getDefault().newWatchService();
@ -49,11 +50,6 @@ public class JfConfigWatchService extends ThreadCoProcess implements Closeable {
@Override
public void executeIteration() {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
return;
}
synchronized (CONFIG_DIR) {
WatchKey key = service.poll();
if (key != null) {

View File

@ -0,0 +1,46 @@
package io.gitlab.jfronny.libjf.config.impl.watch;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import io.gitlab.jfronny.libjf.coprocess.ThreadCoProcess;
import java.io.Closeable;
import java.io.IOException;
public class ToggleableConfigWatchService extends ThreadCoProcess implements Closeable {
private JfConfigWatchService delegate = null;
@Override
public void executeIteration() {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
return;
}
if (ConfigCore.watchForChanges) {
if (delegate == null) {
delegate = new JfConfigWatchServiceImpl();
LibJf.LOGGER.info("Created config watch service");
}
delegate.executeIteration();
} else {
if (delegate != null) {
try {
delegate.close();
} catch (IOException ignored) {
}
delegate = null;
LibJf.LOGGER.info("Discarded config watch service implementation");
}
}
}
@Override
public void close() throws IOException {
if (delegate != null) {
delegate.close();
delegate = null;
LibJf.LOGGER.info("Discarded config watch service");
}
}
}

View File

@ -0,0 +1,5 @@
{
"libjf-config-core-v1.jfconfig.title": "LibJF Config",
"libjf-config-core-v1.jfconfig.watchForChanges": "Watch for changes",
"libjf-config-core-v1.jfconfig.watchForChanges.tooltip": "Automatically reload configs when they are changed"
}

View File

@ -14,7 +14,7 @@
"environment": "*",
"entrypoints": {
"preLaunch": ["io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe"],
"libjf:coprocess": ["io.gitlab.jfronny.libjf.config.impl.JfConfigWatchService"],
"libjf:coprocess": ["io.gitlab.jfronny.libjf.config.impl.watch.ToggleableConfigWatchService"],
"libjf:config_screen": ["io.gitlab.jfronny.libjf.config.impl.ui.PlaceholderScreenFactory"],
"modmenu": ["io.gitlab.jfronny.libjf.config.impl.ui.ModMenuAdapter"]
},

View File

@ -4,7 +4,7 @@ import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v1.*;
import io.gitlab.jfronny.libjf.config.api.v1.dsl.DSL;
import io.gitlab.jfronny.libjf.config.api.v1.reflect.ReflectiveConfigBuilder;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
@ -14,14 +14,13 @@ public class JfConfigReflectSafe implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
LibJf.setup();
for (EntrypointContainer<Object> config : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, Object.class)) {
for (EntrypointContainer<Object> config : FabricLoader.getInstance().getEntrypointContainers(ConfigCore.MODULE_ID, Object.class)) {
registerIfMissing(config.getProvider().getMetadata().getId(), config.getEntrypoint());
}
}
public static void registerIfMissing(String modId, Object config) {
if (!JfConfigSafe.REGISTERED_MODS.contains(modId)) {
JfConfigSafe.REGISTERED_MODS.add(modId);
if (JfConfigSafe.REGISTERED_MODS.add(modId)) {
ConfigHolder.getInstance().migrateFiles(modId);
if (config instanceof JfCustomConfig cfg) {
cfg.register(DSL.create(modId));

View File

@ -1,14 +1,14 @@
package io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import io.gitlab.jfronny.libjf.unsafe.DynamicEntry;
import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
public class JfConfigUnsafe implements UltraEarlyInit {
@Override
public void init() {
DynamicEntry.execute(ConfigHolderImpl.MODULE_ID, Object.class,
DynamicEntry.execute(ConfigCore.MODULE_ID, Object.class,
s -> JfConfigReflectSafe.registerIfMissing(s.modId(), s.instance())
);
LibJf.LOGGER.info("Finished LibJF config entrypoint");