[config] Implement config DSL and rewrite reflection implementation

This commit is contained in:
Johannes Frohnmeyer 2022-08-21 22:34:55 +02:00
parent bbe4213a68
commit 1ce7ddaf38
Signed by: Johannes
GPG Key ID: E76429612C2929F4
82 changed files with 1494 additions and 747 deletions

View File

@ -1,6 +1,8 @@
apply from: "https://jfmods.gitlab.io/scripts/jfmod.gradle"
allprojects {
if (project.name in rootProject.nonModSubprojects) return
loom {
runs {
testmodClient {
@ -21,5 +23,7 @@ allprojects {
dependencies {
testmodRuntimeOnly("com.terraformersmc:modmenu:4.0.5")
testmodRuntimeOnly("net.fabricmc.fabric-api:fabric-api:$project.fabric_version")
compileOnly("io.gitlab.jfronny:commons:$rootProject.commons_version")
compileOnly("io.gitlab.jfronny:commons-gson:$rootProject.commons_version")
}
}

View File

@ -20,7 +20,7 @@ repositories {
and include LibJF modules like this:
```groovy
dependencies {
include modImplementation("io.gitlab.jfronny.libjf:libjf-config-v0:${project.jfapi_version}")
include modImplementation("io.gitlab.jfronny.libjf:libjf-config-v1:${project.jfapi_version}")
include("io.gitlab.jfronny.libjf:libjf-unsafe-v0:${project.jfapi_version}")
include("io.gitlab.jfronny.libjf:libjf-base:${project.jfapi_version}")
modRuntimeOnly("io.gitlab.jfronny.libjf:libjf-devutil-v0:${project.jfapi_version}")

View File

@ -1,7 +1,7 @@
# libjf-web-v0
libjf-web-v0 provides an HTTP web server you can use in your serverside (and technically also clientside) mods
to serve web content through a unified port.
libjf-web-v0 depends on libjf-config-v0 to provide its config, libjf-base, fabric-lifecycle-events-v1 and fabric-command-api-v1
libjf-web-v0 depends on libjf-config-v1 to provide its config, libjf-base, fabric-lifecycle-events-v1 and fabric-command-api-v1
### Getting started
Implement WebInit and register it as a libjf:web entrypoint. To enable the server, also add the following to your fabric.mod.json:

View File

@ -2,15 +2,17 @@
minecraft_version=1.19.2
yarn_mappings=build.8
loader_version=0.14.9
fabric_version=0.60.0+1.19.2
maven_group=io.gitlab.jfronny.libjf
archive_base_name=libjf
dev_only_module=libjf-devutil-v0
modrinth_id=WKwQAwke
modrinth_optional_dependencies=P7dR8mSH
dev_only_module=libjf-devutil-v0
non_mod_project=libjf-config-plugin
modrinth_id=libjf
modrinth_optional_dependencies=fabric-api
curseforge_id=482600
curseforge_optional_dependencies=fabric-api
fabric_version=0.60.0+1.19.2
commons_version=2022.7.4+11-13-3

View File

@ -3,13 +3,13 @@ package io.gitlab.jfronny.libjf;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.gson.GsonBuilder;
import io.gitlab.jfronny.libjf.coprocess.CoProcessManager;
import io.gitlab.jfronny.libjf.gson.GsonAdapter;;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
public class LibJf {
private LibJf() {
}
public class LibJf implements ModInitializer {
public static final String MOD_ID = "libjf";
public static final Logger LOGGER = Logger.forName(MOD_ID);
@ -23,4 +23,9 @@ public class LibJf {
HiddenAnnotationExclusionStrategy.register();
GsonHolder.register();
}
@Override
public void onInitialize() {
Logger.resetFactory();
}
}

View File

@ -18,7 +18,10 @@
"fabric-lifecycle-events-v1": "*"
},
"entrypoints": {
"main": ["io.gitlab.jfronny.libjf.coprocess.CoProcessManager"]
"main": [
"io.gitlab.jfronny.libjf.LibJf",
"io.gitlab.jfronny.libjf.coprocess.CoProcessManager"
]
},
"custom": {
"modmenu": {

View File

@ -0,0 +1,7 @@
archivesBaseName = "libjf-config-reflect-v0"
dependencies {
api project(":libjf-base")
api project(":libjf-unsafe-v0")
api project(":libjf-config-v1")
}

View File

@ -0,0 +1,190 @@
package io.gitlab.jfronny.libjf.config.impl.reflect;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.api.dsl.*;
import io.gitlab.jfronny.libjf.config.api.type.Type;
import io.gitlab.jfronny.libjf.config.impl.*;
import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import io.gitlab.jfronny.libjf.gson.FabricLoaderGsonGenerator;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.CustomValue;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import static io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl.MODULE_ID;
public class ConfigProvider {
public static final String CONFIG_PRESET_DEFAULT = "libjf-config-v1.default";
public static <T extends JfConfig> void register(String id, Class<T> klazz) {
Optional<ModContainer> container = FabricLoader.getInstance().getModContainer(id);
List<String> previousNames = new LinkedList<>();
var metaRef = new Object() {
AuxiliaryMetadata meta = new AuxiliaryMetadata();
};
if (container.isPresent()) {
CustomValue cv = container.get().getMetadata().getCustomValue(MODULE_ID);
if (cv == null) {
cv = container.get().getMetadata().getCustomValue("libjf");
if (cv != null) {
cv = cv.getAsObject().get("config");
}
}
if (cv != null) metaRef.meta = GsonHolder.getGson().fromJson(FabricLoaderGsonGenerator.toGson(cv), AuxiliaryMetadata.class);
previousNames.addAll(container.get().getMetadata().getProvides());
}
else {
LibJf.LOGGER.warn("Attempted to register config for a mod that is not installed: " + id);
}
previousNames.add(id);
Path cfg = FabricLoader.getInstance().getConfigDir();
Path path = cfg.resolve(id + ".json5");
if (!Files.exists(path)) {
try {
for (String s : previousNames) {
Path previousPath = cfg.resolve(s + ".json");
if (Files.exists(previousPath)) {
Files.move(previousPath, path);
break;
}
previousPath = cfg.resolve(s + ".json5");
if (Files.exists(previousPath)) {
Files.move(previousPath, path);
break;
}
}
} catch (IOException e) {
LibJf.LOGGER.error("Could not upgrade config directory", e);
}
}
DSL.create(id).register(builder -> (ConfigBuilder<?>) applyCategory(builder
.setLoadMethod(c -> {
if (Files.exists(path)) {
try (BufferedReader br = Files.newBufferedReader(path)) {
JsonElement element = JsonParser.parseReader(br);
if (element.isJsonObject()) loadFrom(element.getAsJsonObject(), c);
else LibJf.LOGGER.error("Invalid config: Not a JSON object for " + id);
} catch (Exception e) {
LibJf.LOGGER.error("Could not read config for " + id, e);
}
}
c.write();
})
.setWriteMethod(c -> JfConfigWatchService.lock(path, () -> {
try (BufferedWriter bw = Files.newBufferedWriter(path);
JsonWriter jw = GsonHolder.getGson().newJsonWriter(bw)) {
writeTo(jw, c);
} catch (Exception e) {
LibJf.LOGGER.error("Could not write config", e);
}
}))
.setPath(path),
klazz, id, metaRef.meta));
}
private static CategoryBuilder<?> applyCategory(CategoryBuilder<?> builder, Class<?> configClass, String modId, AuxiliaryMetadata meta) {
if (meta.referencedConfigs != null) meta.referencedConfigs.forEach(builder::referenceConfig);
for (Field field : configClass.getFields()) {
if (field.isAnnotationPresent(Entry.class)) {
Entry entry = field.getAnnotation(Entry.class);
Object defaultValue = null;
try {
defaultValue = field.get(null);
} catch (IllegalAccessException ignored) {
}
//noinspection unchecked,rawtypes
builder.value(new DslEntryInfo<Object>(
field.getName(),
defaultValue,
() -> field.get(null),
v -> field.set(null, v),
(Type) Type.ofClass(field.getGenericType()),
entry == null ? 100 : entry.width(),
entry == null ? Double.NEGATIVE_INFINITY : entry.min(),
entry == null ? Double.POSITIVE_INFINITY : entry.max()
));
}
}
builder.addPreset(CONFIG_PRESET_DEFAULT, ConfigCategory::reset);
for (Method method : configClass.getMethods()) {
if (method.isAnnotationPresent(Preset.class)) {
builder.addPreset(builder.getTranslationPrefix() + method.getName(), c -> {
try {
method.invoke(null);
} catch (InvocationTargetException | IllegalAccessException e) {
LibJf.LOGGER.error("Could not apply preset", e);
}
});
} else if (method.isAnnotationPresent(Verifier.class)) {
builder.addVerifier(c -> {
try {
method.invoke(null);
} catch (InvocationTargetException | IllegalAccessException e) {
LibJf.LOGGER.error("Could not run verifier", e);
}
});
}
}
for (Class<?> categoryClass : configClass.getClasses()) {
if (categoryClass.isAnnotationPresent(Category.class)) {
String name = categoryClass.getSimpleName();
name = Character.toLowerCase(name.charAt(0)) + name.substring(1); // camelCase
//TODO allow custom auxiliary metadata
builder.category(name, builder1 -> applyCategory(builder1, categoryClass, modId, new AuxiliaryMetadata()));
}
}
return builder;
}
public static void loadFrom(JsonObject source, ConfigCategory category) {
for (EntryInfo<?> entry : category.getEntries()) {
if (source.has(entry.getName())) {
try {
entry.loadFromJson(source.get(entry.getName()));
} catch (IllegalAccessException e) {
LibJf.LOGGER.error("Could not set config entry value of " + entry.getName(), e);
}
} else LibJf.LOGGER.error("Config does not contain entry for " + entry.getName());
}
for (Map.Entry<String, ConfigCategory> entry : category.getCategories().entrySet()) {
if (source.has(entry.getKey())) {
JsonElement el = source.get(entry.getKey());
if (el.isJsonObject()) loadFrom(el.getAsJsonObject(), entry.getValue());
else LibJf.LOGGER.error("Config category is not a JSON object, skipping");
} else LibJf.LOGGER.error("Config does not contain entry for subcategory " + entry.getKey());
}
}
public static void writeTo(JsonWriter writer, ConfigCategory category) throws IOException {
category.fix();
writer.beginObject();
String val;
for (EntryInfo<?> entry : category.getEntries()) {
try {
entry.writeTo(writer, category.getTranslationPrefix());
} catch (IllegalAccessException e) {
LibJf.LOGGER.error("Could not write entry", e);
}
}
for (Map.Entry<String, ConfigCategory> entry : category.getCategories().entrySet()) {
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(category.getTranslationPrefix() + entry.getKey() + ".title")) != null)
writer.comment(val);
writer.name(entry.getKey());
writeTo(writer, entry.getValue());
}
writer.endObject();
}
}

View File

@ -0,0 +1,26 @@
package io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import io.gitlab.jfronny.libjf.config.impl.reflect.ConfigProvider;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
public class JfConfigReflectSafe implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
for (EntrypointContainer<JfConfig> config : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, JfConfig.class)) {
registerIfMissing(config.getProvider().getMetadata().getId(), config.getEntrypoint().getClass());
}
}
public static void registerIfMissing(String modId, Class<? extends JfConfig> klazz) {
if (!ConfigHolder.getInstance().isRegistered(modId)) {
LibJf.LOGGER.info("Registering config for " + modId);
ConfigProvider.register(modId, klazz);
}
}
}

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.config.api.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
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, JfConfig.class,
s -> JfConfigReflectSafe.registerIfMissing(s.modId(), s.instance().getClass())
);
DynamicEntry.execute(ConfigHolderImpl.CUSTOM_ID, JfCustomConfig.class,
s -> s.instance().register(DSL.create(s.modId()))
);
LibJf.LOGGER.info("Finished LibJF config entrypoint");
}
}

View File

@ -0,0 +1,36 @@
{
"schemaVersion": 1,
"id": "libjf-config-reflect-v0",
"name": "LibJF Config Reflect",
"version": "${version}",
"authors": ["JFronny"],
"contact": {
"website": "https://jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/libjf"
},
"license": "MIT",
"environment": "*",
"entrypoints": {
"libjf:preEarly": [
"io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint.JfConfigUnsafe"
],
"preLaunch": [
"io.gitlab.jfronny.libjf.config.impl.reflect.entrypoint.JfConfigReflectSafe"
]
},
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"fabric-resource-loader-v0": "*",
"fabric-command-api-v2": "*",
"libjf-base": ">=${version}",
"libjf-unsafe-v0": ">=${version}",
"libjf-config-v1": ">=${version}"
},
"custom": {
"modmenu": {
"badges": ["library"],
"parent": "libjf"
}
}
}

View File

@ -0,0 +1,13 @@
{
"libjf-config-v1-testmod.jfconfig.title": "JfConfig example",
"libjf-config-v1-testmod.jfconfig.disablePacks": "Disable resource packs",
"libjf-config-v1-testmod.jfconfig.intTest": "Int Test",
"libjf-config-v1-testmod.jfconfig.decimalTest": "Decimal Test",
"libjf-config-v1-testmod.jfconfig.dieStr": "String Test",
"libjf-config-v1-testmod.jfconfig.gsonOnlyStr.tooltip": "George",
"libjf-config-v1-testmod.jfconfig.enumTest": "Enum Test",
"libjf-config-v1-testmod.jfconfig.enumTest.tooltip": "Enum Test Tooltip",
"libjf-config-v1-testmod.jfconfig.enum.Test.Test": "Test",
"libjf-config-v1-testmod.jfconfig.enum.Test.ER": "ER",
"libjf-config-v1-testmod.jfconfig.moskau": "Moskau"
}

View File

@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"id": "libjf-config-v0-testmod",
"id": "libjf-config-reflect-v0-testmod",
"version": "1.0",
"environment": "*",
"entrypoints": {

View File

@ -1,31 +0,0 @@
package io.gitlab.jfronny.libjf.config.api;
import java.util.List;
import java.util.Map;
public interface ConfigInstance {
static ConfigInstance get(Class<?> configClass) {
return ConfigHolder.getInstance().get(configClass);
}
static ConfigInstance get(String modId) {
return ConfigHolder.getInstance().get(modId);
}
void load();
void write();
String getId();
String getCategoryPath();
default String getTranslationPrefix() {
return getId() + ".jfconfig." + getCategoryPath();
}
List<EntryInfo<?>> getEntries();
Map<String, Runnable> getPresets();
default List<ConfigInstance> getReferencedConfigs() {
return List.of();
}
Map<String, ConfigInstance> getCategories();
default void fix() {
for (EntryInfo<?> entry : getEntries()) {
entry.fix();
}
}
}

View File

@ -1,100 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl;
import com.google.common.collect.*;
import io.gitlab.jfronny.commons.serialize.gson.api.*;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.gson.*;
import io.gitlab.jfronny.libjf.unsafe.*;
import net.fabricmc.loader.api.*;
import net.fabricmc.loader.api.metadata.*;
import org.jetbrains.annotations.*;
import java.nio.file.*;
import java.util.*;
public class ConfigHolderImpl implements ConfigHolder {
@ApiStatus.Internal
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<>();
@Override
public void register(String modId, Class<?> config) {
if (isRegistered(modId)) {
if (get(modId) instanceof ConfigInstanceAbstract instance && instance.matchesConfigClass(config)) {
SafeLog.warn("Attempted to set config of " + modId + " twice, skipping");
return;
}
SafeLog.warn("Overriding config class of " + modId + " to " + config);
}
if (isRegistered(config)) {
SafeLog.warn("Attempted to reuse config class " + config + ", this is unsupported");
}
Optional<ModContainer> container = FabricLoader.getInstance().getModContainer(modId);
List<String> previousNames = new LinkedList<>();
AuxiliaryMetadata meta = new AuxiliaryMetadata();
if (container.isPresent()) {
CustomValue cv = container.get().getMetadata().getCustomValue(MODULE_ID);
if (cv == null) {
cv = container.get().getMetadata().getCustomValue("libjf");
if (cv != null) {
cv = cv.getAsObject().get("config");
}
}
if (cv != null) meta = GsonHolder.getGson().fromJson(FabricLoaderGsonGenerator.toGson(cv), AuxiliaryMetadata.class);
previousNames.addAll(container.get().getMetadata().getProvides());
}
else {
SafeLog.warn("Attempted to register config for a mod that is not installed: " + modId);
}
ConfigInstanceRoot instance = new ConfigInstanceRoot(modId, previousNames, config, meta.sanitize());
configs.put(modId, instance);
configsByPath.put(instance.path, instance);
}
@Override
public Map<String, ConfigInstance> getRegistered() {
return ImmutableMap.copyOf(configs);
}
@Override
public ConfigInstance get(Class<?> configClass) {
for (ConfigInstance value : configs.values()) {
if (value instanceof ConfigInstanceAbstract instance && instance.matchesConfigClass(configClass))
return value;
}
return null;
}
@Override
public ConfigInstance get(String configClass) {
return configs.get(configClass);
}
@Override
public ConfigInstance get(Path configPath) {
return configsByPath.get(configPath);
}
@Override
public boolean isRegistered(Class<?> config) {
for (ConfigInstance value : configs.values()) {
if (value instanceof ConfigInstanceAbstract instance && instance.matchesConfigClass(config))
return true;
}
return false;
}
@Override
public boolean isRegistered(String modId) {
return configs.containsKey(modId);
}
@Override
public boolean isRegistered(Path configPath) {
return configsByPath.containsKey(configPath);
}
}

View File

@ -1,170 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.JsonObject;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
public abstract class ConfigInstanceAbstract implements ConfigInstance {
public static final String CONFIG_PRESET_DEFAULT = "libjf-config-v0.default";
public final String modId;
private final String categoryPath;
public final Class<?> configClass;
public final List<String> referencedConfigs;
public final List<EntryInfo<?>> entries = new ArrayList<>();
public final Map<String, Runnable> presets = new LinkedHashMap<>();
public final Set<Runnable> verifiers = new LinkedHashSet<>();
public final Map<String, ConfigInstance> subcategories = new LinkedHashMap<>();
public ConfigInstanceAbstract(String modId, String categoryPath, Class<?> configClass, AuxiliaryMetadata meta) {
this.modId = modId;
this.categoryPath = categoryPath;
this.configClass = configClass;
this.referencedConfigs = List.copyOf(meta.referencedConfigs);
for (Field field : configClass.getFields()) {
if (field.isAnnotationPresent(Entry.class))
entries.add(new EntryInfoImpl<>(field));
}
presets.put(CONFIG_PRESET_DEFAULT, () -> {
for (EntryInfo<?> entry : entries) {
try {
reset(entry);
} catch (IllegalAccessException e) {
SafeLog.error("Could not reload default values", e);
}
}
});
for (Method method : configClass.getMethods()) {
if (method.isAnnotationPresent(Preset.class)) {
presets.put(modId + ".jfconfig." + method.getName(), () -> {
try {
method.invoke(null);
} catch (IllegalAccessException | InvocationTargetException e) {
SafeLog.error("Could not apply preset", e);
}
});
}
else if (method.isAnnotationPresent(Verifier.class)) {
verifiers.add(() -> {
try {
method.invoke(null);
} catch (IllegalAccessException | InvocationTargetException e) {
SafeLog.error("Could not run verifier", e);
}
});
}
}
for (Class<?> categoryClass : configClass.getClasses()) {
if (categoryClass.isAnnotationPresent(Category.class)) {
String name = camelCase(categoryClass.getSimpleName());
//TODO allow custom auxiliary metadata
subcategories.put(name, new ConfigInstanceCategory(this, modId, categoryPath + name + ".", categoryClass, new AuxiliaryMetadata().sanitize()));
}
}
}
// Here due to generics issues
private <T> void reset(EntryInfo<T> info) throws IllegalAccessException {
info.setValue(info.getDefault());
}
private String camelCase(String source) {
if (source == null) return null;
if (source.length() == 0) return source;
return Character.toLowerCase(source.charAt(0)) + source.substring(1);
}
public void loadFrom(JsonObject source) {
for (EntryInfo<?> entry : entries) {
if (source.has(entry.getName())) {
try {
entry.loadFromJson(source.get(entry.getName()));
} catch (IllegalAccessException e) {
SafeLog.error("Could not set config entry value of " + entry.getName(), e);
}
} else SafeLog.error("Config does not contain entry for " + entry.getName());
}
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
if (entry.getValue() instanceof ConfigInstanceAbstract instance && source.has(entry.getKey())) {
JsonElement el = source.get(entry.getKey());
if (el.isJsonObject()) instance.loadFrom(el.getAsJsonObject());
else SafeLog.error("Config category is not a JSON object, skipping");
} else SafeLog.error("Config does not contain entry for subcategory " + entry.getKey());
}
}
public void writeTo(JsonWriter writer) throws IOException {
fix();
writer.beginObject();
String val;
for (EntryInfo<?> entry : entries) {
try {
entry.writeTo(writer, modId + ".jfconfig." + categoryPath);
} catch (IllegalAccessException e) {
SafeLog.error("Could not write entry", e);
}
}
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
if (!(entry.getValue() instanceof ConfigInstanceAbstract instance)) continue;
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(modId + ".jfconfig." + categoryPath + entry.getKey() + ".title")) != null)
writer.comment(val);
writer.name(entry.getKey());
instance.writeTo(writer);
}
writer.endObject();
}
@Override
public void fix() {
for (Runnable verifier : verifiers) verifier.run();
for (EntryInfo<?> entry : entries) entry.fix();
}
public boolean matchesConfigClass(Class<?> candidate) {
return candidate != null && candidate.isAssignableFrom(configClass);
}
@Override
public String getId() {
return modId;
}
@Override
public String getCategoryPath() {
return categoryPath;
}
@Override
public List<EntryInfo<?>> getEntries() {
return entries;
}
@Override
public Map<String, Runnable> getPresets() {
return presets;
}
@Override
public List<ConfigInstance> getReferencedConfigs() {
List<ConfigInstance> result = new LinkedList<>();
for (String referencedConfig : referencedConfigs) {
ConfigInstance ci = ConfigHolder.getInstance().get(referencedConfig);
if (ci != null) result.add(ci);
}
return result;
}
@Override
public Map<String, ConfigInstance> getCategories() {
return subcategories;
}
}

View File

@ -1,22 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
public class ConfigInstanceCategory extends ConfigInstanceAbstract {
private final ConfigInstance parent;
public ConfigInstanceCategory(ConfigInstance parent, String modId, String categoryPath, Class<?> configClass, AuxiliaryMetadata meta) {
super(modId, categoryPath, configClass, meta);
this.parent = parent;
}
@Override
public void load() {
parent.load();
}
@Override
public void write() {
parent.write();
}
}

View File

@ -1,77 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.JsonParser;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import net.fabricmc.loader.api.FabricLoader;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
/** Based on https://github.com/TeamMidnightDust/MidnightLib which is based on https://github.com/Minenash/TinyConfig
* Credits to TeamMidnightDust and Minenash */
public class ConfigInstanceRoot extends ConfigInstanceAbstract {
public final Path path;
public ConfigInstanceRoot(String modId, Collection<String> previousNames, Class<?> config, AuxiliaryMetadata meta) {
super(modId, "", config, meta);
Path cfg = FabricLoader.getInstance().getConfigDir();
path = cfg.resolve(modId + ".json5");
previousNames = new ArrayList<>(previousNames);
previousNames.add(modId);
if (!Files.exists(path)) {
try {
for (String s : previousNames) {
Path previousPath = cfg.resolve(s + ".json");
if (Files.exists(previousPath)) {
Files.move(previousPath, path);
break;
}
previousPath = cfg.resolve(s + ".json5");
if (Files.exists(previousPath)) {
Files.move(previousPath, path);
break;
}
}
} catch (IOException e) {
SafeLog.error("Could not upgrade config directory", e);
}
}
load();
}
@Override
public void load() {
if (Files.exists(path)) {
try (BufferedReader br = Files.newBufferedReader(path)) {
JsonElement element = JsonParser.parseReader(br);
if (element.isJsonObject()) loadFrom(element.getAsJsonObject());
else SafeLog.error("Invalid config: Not a JSON object for " + modId);
}
catch (Exception e) {
SafeLog.error("Could not read config for " + modId, e);
}
}
write();
}
@Override
public void write() {
JfConfigWatchService.lock(path, () -> {
try (BufferedWriter bw = Files.newBufferedWriter(path);
JsonWriter jw = GsonHolder.getGson().newJsonWriter(bw)) {
writeTo(jw);
} catch (Exception e) {
SafeLog.error("Could not write config", e);
}
});
}
}

View File

@ -1,113 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.commons.serialize.gson.api.*;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.*;
import io.gitlab.jfronny.libjf.unsafe.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;
public class EntryInfoImpl<T> implements EntryInfo<T> {
public Field field;
public T defaultValue;
public Entry entry;
public EntryInfoImpl(Field field) {
this.field = field;
this.entry = field.getAnnotation(Entry.class);
try {
this.defaultValue = (T) field.get(null);
} catch (IllegalAccessException ignored) {}
}
@Override
public T getDefault() {
return defaultValue;
}
@Override
public T getValue() throws IllegalAccessException {
return (T) field.get(null);
}
@Override
public void setValue(T value) throws IllegalAccessException {
field.set(null, value);
}
@Override
public Class<T> getValueType() {
return (Class<T>) field.getType();
}
@Override
public void fix() {
Object value;
try {
value = field.get(null);
} catch (IllegalAccessException e) {
SafeLog.error("Could not read value", e);
return;
}
final Object valueOriginal = value;
if (value instanceof final Integer v) {
if (v < entry.min()) value = (int)entry.min();
if (v > entry.max()) value = (int)entry.max();
} else if (value instanceof final Float v) {
if (v < entry.min()) value = (float)entry.min();
if (v > entry.max()) value = (float)entry.max();
} else if (value instanceof final Double v) {
if (v < entry.min()) value = entry.min();
if (v > entry.max()) value = entry.max();
}
if (valueOriginal != value) {
try {
field.set(null, value);
} catch (IllegalAccessException e) {
SafeLog.error("Could not write value", e);
}
}
}
@Override
public String getName() {
return field.getName();
}
@Override
public void loadFromJson(JsonElement element) throws IllegalAccessException {
setValue(GsonHolder.getGson().fromJson(element, field.getGenericType()));
}
@Override
public void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException {
T value = getValue();
String val;
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(translationPrefix + getName() + ".tooltip")) != null)
writer.comment(val);
if (field.getType().isEnum())
writer.comment("Valid: [" + Arrays.stream(field.getType().getEnumConstants()).map(Objects::toString).collect(Collectors.joining(", ")) + "]");
writer.name(getName());
GsonHolder.getGson().toJson(value, field.getGenericType(), writer);
}
@Override
public int getWidth() {
return entry.width();
}
@Override
public double getMinValue() {
return entry.min();
}
@Override
public double getMaxValue() {
return entry.max();
}
}

View File

@ -1,31 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl.entrypoint;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.impl.*;
import io.gitlab.jfronny.libjf.unsafe.*;
import net.fabricmc.loader.api.*;
import net.fabricmc.loader.api.entrypoint.*;
import net.minecraft.util.*;
import java.util.function.*;
public class JfConfigSafe implements PreLaunchEntrypoint {
public static Function<String, String> TRANSLATION_SUPPLIER = s -> null;
@Override
public void onPreLaunch() {
for (EntrypointContainer<JfConfig> config : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.MODULE_ID, JfConfig.class)) {
registerIfMissing(config.getProvider().getMetadata().getId(), config.getEntrypoint().getClass());
}
TRANSLATION_SUPPLIER = s -> {
String translated = Language.getInstance().get(s);
return translated.equals(s) ? null : translated;
};
}
public static void registerIfMissing(String modId, Class<?> klazz) {
if (!ConfigHolder.getInstance().isRegistered(modId)) {
SafeLog.info("Registering config for " + modId);
ConfigHolder.getInstance().register(modId, klazz);
}
}
}

View File

@ -1,17 +0,0 @@
package io.gitlab.jfronny.libjf.config.impl.entrypoint;
import io.gitlab.jfronny.libjf.config.api.JfConfig;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import io.gitlab.jfronny.libjf.unsafe.DynamicEntry;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
public class JfConfigUnsafe implements UltraEarlyInit {
@Override
public void init() {
DynamicEntry.execute(ConfigHolderImpl.MODULE_ID, JfConfig.class,
s -> JfConfigSafe.registerIfMissing(s.modId(), s.instance().getClass())
);
SafeLog.info("Finished LibJF config entrypoint");
}
}

View File

@ -1,6 +0,0 @@
{
"libjf-config-v0.presets": "Presets",
"libjf-config-v0.default": "Default",
"libjf-config-v0.see-also": "See also: %s",
"libjf-config-v0.reset": "Reset"
}

View File

@ -1,13 +0,0 @@
{
"libjf-config-v0-testmod.jfconfig.title": "JfConfig example",
"libjf-config-v0-testmod.jfconfig.disablePacks": "Disable resource packs",
"libjf-config-v0-testmod.jfconfig.intTest": "Int Test",
"libjf-config-v0-testmod.jfconfig.decimalTest": "Decimal Test",
"libjf-config-v0-testmod.jfconfig.dieStr": "String Test",
"libjf-config-v0-testmod.jfconfig.gsonOnlyStr.tooltip": "George",
"libjf-config-v0-testmod.jfconfig.enumTest": "Enum Test",
"libjf-config-v0-testmod.jfconfig.enumTest.tooltip": "Enum Test Tooltip",
"libjf-config-v0-testmod.jfconfig.enum.Test.Test": "Test",
"libjf-config-v0-testmod.jfconfig.enum.Test.ER": "ER",
"libjf-config-v0-testmod.jfconfig.moskau": "Moskau"
}

View File

@ -1,7 +1,7 @@
archivesBaseName = "libjf-config-v0"
archivesBaseName = "libjf-config-v1"
dependencies {
moduleDependencies(project, ["libjf-base", "libjf-unsafe-v0"])
api project(":libjf-base")
include fabricApi.module("fabric-resource-loader-v0", "${project.fabric_version}")
include modImplementation(fabricApi.module("fabric-command-api-v2", "${project.fabric_version}"))
modCompileOnly("com.terraformersmc:modmenu:4.0.5")

View File

@ -3,9 +3,8 @@ package io.gitlab.jfronny.libjf.config.impl.client.gui;
import io.gitlab.jfronny.commons.throwable.Try;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.WidgetFactory;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.api.type.Type;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.widget.ButtonWidget;
@ -24,36 +23,33 @@ public class EntryInfoWidgetBuilder {
private static final Pattern INTEGER_ONLY = Pattern.compile("(-?\\d*)");
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)");
public static List<WidgetState<?>> buildWidgets(ConfigInstance config) {
WidgetState<?> state;
public static List<WidgetState<?>> buildWidgets(ConfigCategory config) {
List<WidgetState<?>> knownStates = new ArrayList<>();
for (EntryInfo<?> info : config.getEntries()) {
if ((state = initEntry(config, info, knownStates)) != null) {
knownStates.add(state);
}
knownStates.add(initEntry(config, info, knownStates));
}
return knownStates;
}
private static <T> WidgetState<T> initEntry(ConfigInstance config, EntryInfo<T> info, List<WidgetState<?>> knownStates) {
Class<T> type = info.getValueType();
private static <T> WidgetState<T> initEntry(ConfigCategory config, EntryInfo<T> info, List<WidgetState<?>> knownStates) {
Type<T> type = info.getValueType();
WidgetState<T> state = new WidgetState<>();
WidgetFactory factory;
if (type == int.class || type == Integer.class) factory = textField(info, state, INTEGER_ONLY, Integer::parseInt, true, info.getMinValue(), info.getMaxValue());
else if (type == float.class || type == Float.class) factory = textField(info, state, DECIMAL_ONLY, Float::parseFloat, false, info.getMinValue(), info.getMaxValue());
else if (type == double.class || type == Double.class) factory = textField(info, state, DECIMAL_ONLY, Double::parseDouble, false, info.getMinValue(), info.getMaxValue());
else if (type == String.class) factory = textField(info, state, null, String::length, true, Math.min(info.getMinValue(),0), Math.max(info.getMaxValue(),1));
else if (type == boolean.class || type == Boolean.class) {
if (type.isInt()) factory = textField(info, state, INTEGER_ONLY, Integer::parseInt, true, info.getMinValue(), info.getMaxValue());
else if (type.isFloat()) factory = textField(info, state, DECIMAL_ONLY, Float::parseFloat, false, info.getMinValue(), info.getMaxValue());
else if (type.isDouble()) factory = textField(info, state, DECIMAL_ONLY, Double::parseDouble, false, info.getMinValue(), info.getMaxValue());
else if (type.isString()) factory = textField(info, state, null, String::length, true, Math.min(info.getMinValue(),0), Math.max(info.getMaxValue(),1));
else if (type.isBool()) {
factory = toggle(info, state,
value -> !(Boolean) value,
value -> Text.literal((Boolean) value ? "True" : "False").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED));
} else if (type.isEnum()) {
List<T> values = Arrays.asList(info.getValueType().getEnumConstants());
T[] values = ((Type.TEnum<T>)type).options();
factory = toggle(info, state, value -> {
int index = values.indexOf(value) + 1;
return values.get(index >= values.size() ? 0 : index);
}, value -> Text.translatable(config.getTranslationPrefix() + "enum." + type.getSimpleName() + "." + state.cachedValue));
int index = indexOf(values, value) + 1;
return values[index >= values.length ? 0 : index];
}, value -> Text.translatable(config.getTranslationPrefix() + "enum." + type.getName() + "." + state.cachedValue));
} else {
LibJf.LOGGER.error("Invalid entry type in " + info.getName() + ": " + type.getName());
factory = ((screenWidth, textRenderer, done) -> new WidgetFactory.Widget(() -> {}, new ButtonWidget(-10, 0, 0, 0, Text.of(""), null)));
@ -63,6 +59,13 @@ public class EntryInfoWidgetBuilder {
return state;
}
private static int indexOf(Object[] array, Object value) {
for (int i = 0; i < array.length; i++) {
if (array[i] == value) return i;
}
return -1;
}
private static <T> WidgetFactory toggle(EntryInfo<T> info, WidgetState<T> state, Function<Object, Object> increment, Function<T, Text> valueTextifier) {
return (screenWidth, textRenderer, done) -> {
ButtonWidget button = new ButtonWidget(screenWidth - 110, 0, info.getWidth(), 20, valueTextifier.apply(state.cachedValue), btn -> {

View File

@ -1,10 +1,9 @@
package io.gitlab.jfronny.libjf.config.impl.client.gui;
import io.gitlab.jfronny.commons.throwable.Try;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.WidgetFactory;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.impl.client.gui.presets.PresetsScreen;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
@ -19,7 +18,7 @@ import java.util.*;
@Environment(EnvType.CLIENT)
public class TinyConfigScreen extends Screen {
public TinyConfigScreen(ConfigInstance config, Screen parent) {
public TinyConfigScreen(ConfigCategory config, Screen parent) {
super(Text.translatable(config.getTranslationPrefix() + "title"));
this.parent = parent;
this.config = config;
@ -28,7 +27,7 @@ public class TinyConfigScreen extends Screen {
}
private final String translationPrefix;
private final Screen parent;
private final ConfigInstance config;
private final ConfigCategory config;
private final List<WidgetState<?>> widgets;
private MidnightConfigListWidget list;
@ -38,26 +37,25 @@ public class TinyConfigScreen extends Screen {
config.fix();
this.addDrawableChild(new ButtonWidget(4, 6, 80, 20, Text.translatable("libjf-config-v0.presets"), button -> {
this.addDrawableChild(new ButtonWidget(4, 6, 80, 20, Text.translatable("libjf-config-v1.presets"), button -> {
MinecraftClient.getInstance().setScreen(new PresetsScreen(this, config));
}));
this.addDrawableChild(new ButtonWidget(this.width / 2 - 154, this.height - 28, 150, 20, ScreenTexts.CANCEL, button -> {
config.load();
Objects.requireNonNull(client).setScreen(parent);
}));
ButtonWidget done = this.addDrawableChild(new ButtonWidget(this.width / 2 + 4, this.height - 28, 150, 20, ScreenTexts.DONE, (button) -> {
for (WidgetState<?> state : widgets) {
Try.orElse(state::writeToEntry, e -> SafeLog.error("Could not write config data to class", e));
Try.orElse(state::writeToEntry, e -> LibJf.LOGGER.error("Could not write config data to class", e));
}
config.write();
config.getRoot().write();
Objects.requireNonNull(client).setScreen(parent);
}));
this.list = new MidnightConfigListWidget(this.client, this.width, this.height, 32, this.height - 32, 25);
this.addSelectableChild(this.list);
for (Map.Entry<String, ConfigInstance> entry : config.getCategories().entrySet()) {
for (Map.Entry<String, ConfigCategory> entry : config.getCategories().entrySet()) {
this.list.addReference(width / 2,
Text.translatable(entry.getValue().getTranslationPrefix() + "title"),
() -> new TinyConfigScreen(entry.getValue(), this));
@ -65,7 +63,7 @@ public class TinyConfigScreen extends Screen {
for (WidgetState<?> info : widgets) {
MutableText name = Text.translatable(translationPrefix + info.entry.getName());
WidgetFactory.Widget control = info.factory.build(width, textRenderer, done);
ButtonWidget resetButton = new ButtonWidget(width - 155, 0, 40, 20, Text.translatable("libjf-config-v0.reset"), (button -> {
ButtonWidget resetButton = new ButtonWidget(width - 155, 0, 40, 20, Text.translatable("libjf-config-v1.reset"), (button -> {
info.reset();
control.updateControls().run();
}));
@ -78,7 +76,7 @@ public class TinyConfigScreen extends Screen {
for (ConfigInstance ci : config.getReferencedConfigs()) {
if (ci != null) {
this.list.addReference(width / 2,
Text.translatable("libjf-config-v0.see-also", Text.translatable(ci.getTranslationPrefix() + "title")),
Text.translatable("libjf-config-v1.see-also", Text.translatable(ci.getTranslationPrefix() + "title")),
() -> new TinyConfigScreen(ci, this));
}
}

View File

@ -1,9 +1,9 @@
package io.gitlab.jfronny.libjf.config.impl.client.gui;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.WidgetFactory;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
@ -26,7 +26,7 @@ public class WidgetState<T> {
try {
updateCache(entry.getValue());
} catch (IllegalAccessException e) {
SafeLog.error("Could not create initial widget state cache", e);
LibJf.LOGGER.error("Could not create initial widget state cache", e);
}
}

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.libjf.config.impl.client.gui.presets;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigCategory;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@ -15,11 +16,11 @@ import java.util.Map;
@Environment(EnvType.CLIENT)
public class PresetsScreen extends Screen {
private final Screen parent;
private final ConfigInstance config;
private final ConfigCategory config;
private PresetListWidget list;
public PresetsScreen(Screen parent, ConfigInstance config) {
super(Text.translatable("libjf-config-v0.presets"));
public PresetsScreen(Screen parent, ConfigCategory config) {
super(Text.translatable("libjf-config-v1.presets"));
this.parent = parent;
this.config = config;
}

View File

@ -0,0 +1,33 @@
package io.gitlab.jfronny.libjf.config.api;
import io.gitlab.jfronny.libjf.LibJf;
import java.util.List;
import java.util.Map;
public interface ConfigCategory {
String getId();
String getCategoryPath();
default String getTranslationPrefix() {
return getId() + ".jfconfig." + getCategoryPath();
}
List<EntryInfo<?>> getEntries();
Map<String, Runnable> getPresets();
List<ConfigInstance> getReferencedConfigs();
Map<String, ConfigCategory> getCategories();
ConfigInstance getRoot();
default void fix() {
for (EntryInfo<?> entry : getEntries()) {
entry.fix();
}
}
default void reset() {
for (EntryInfo<?> entry : getEntries()) {
try {
entry.reset();
} catch (IllegalAccessException e) {
LibJf.LOGGER.error("Could not reload default values", e);
}
}
}
}

View File

@ -9,12 +9,10 @@ public interface ConfigHolder {
static ConfigHolder getInstance() {
return ConfigHolderImpl.INSTANCE;
}
void register(String modId, Class<?> config);
void register(String modId, ConfigInstance config);
Map<String, ConfigInstance> getRegistered();
ConfigInstance get(Class<?> configClass);
ConfigInstance get(String modId);
ConfigInstance get(Path configPath);
boolean isRegistered(Class<?> configClass);
boolean isRegistered(String modId);
boolean isRegistered(Path configPath);
}

View File

@ -0,0 +1,13 @@
package io.gitlab.jfronny.libjf.config.api;
import java.nio.file.Path;
import java.util.*;
public interface ConfigInstance extends ConfigCategory {
static ConfigInstance get(String modId) {
return ConfigHolder.getInstance().get(modId);
}
void load();
void write();
Optional<Path> getFilePath();
}

View File

@ -2,10 +2,17 @@ package io.gitlab.jfronny.libjf.config.api;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.config.api.type.Type;
import java.io.IOException;
public interface EntryInfo<T> {
/**
* Get the name of this entry
* @return This entry's name
*/
String getName();
/**
* @return Get the default value of this entry
*/
@ -27,19 +34,13 @@ public interface EntryInfo<T> {
* Get the value type of this entry. Will use the class definition, not the current value
* @return The type of this entry
*/
Class<T> getValueType();
Type<T> getValueType();
/**
* Ensure the current value is within expected bounds.
*/
void fix();
/**
* Get the name of this entry
* @return This entry's name
*/
String getName();
/**
* Set this entry's value to that of the element
* @param element The element to read from
@ -66,4 +67,8 @@ public interface EntryInfo<T> {
* @return Get the maximum value for this entry
*/
double getMaxValue();
default void reset() throws IllegalAccessException {
setValue(getDefault());
}
}

View File

@ -0,0 +1,7 @@
package io.gitlab.jfronny.libjf.config.api;
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
public interface JfCustomConfig {
void register(DSL.Defaulted dsl);
}

View File

@ -0,0 +1,33 @@
package io.gitlab.jfronny.libjf.config.api.dsl;
import io.gitlab.jfronny.libjf.config.api.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
public interface CategoryBuilder<Builder extends CategoryBuilder<Builder>> {
Builder setTranslationPrefix(String translationPrefix);
String getTranslationPrefix();
Builder addPreset(String id, Consumer<ConfigCategory> action);
Builder addVerifier(Consumer<ConfigCategory> verifier);
Builder referenceConfig(String id);
Builder referenceConfig(ConfigInstance config);
Builder category(String id, CategoryBuilderFunction builder);
Builder value(String id, int def, double min, double max, Supplier<Integer> get, Consumer<Integer> set);
Builder value(String id, float def, double min, double max, Supplier<Float> get, Consumer<Float> set);
Builder value(String id, double def, double min, double max, Supplier<Double> get, Consumer<Double> set);
Builder value(String id, String def, Supplier<String> get, Consumer<String> set);
Builder value(String id, boolean def, Supplier<Boolean> get, Consumer<Boolean> set);
Builder value(String id, String def, String[] options, Supplier<String> get, Consumer<String> set);
<T extends Enum<T>> Builder value(String id, T def, Class<T> klazz, Supplier<T> get, Consumer<T> set);
<T> Builder value(EntryInfo<T> entry);
String getId();
ConfigCategory build(Supplier<ConfigInstance> root);
@FunctionalInterface
interface CategoryBuilderFunction {
CategoryBuilder<?> apply(CategoryBuilder<?> builder);
}
}

View File

@ -0,0 +1,19 @@
package io.gitlab.jfronny.libjf.config.api.dsl;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import java.nio.file.Path;
import java.util.function.Consumer;
public interface ConfigBuilder<Builder extends ConfigBuilder<Builder>> extends CategoryBuilder<Builder> {
Builder setLoadMethod(Consumer<ConfigInstance> load);
Builder setWriteMethod(Consumer<ConfigInstance> write);
Builder setPath(Path path);
ConfigInstance build();
@FunctionalInterface
interface ConfigBuilderFunction {
ConfigBuilder<?> apply(ConfigBuilder<?> builder);
}
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.libjf.config.api.dsl;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.impl.dsl.DSLImpl;
public interface DSL {
static DSL create() {
return new DSLImpl();
}
static DSL.Defaulted create(String defaultId) {
return new DSLImpl.Defaulted(defaultId);
}
ConfigInstance config(String configId, ConfigBuilder.ConfigBuilderFunction builder);
ConfigInstance register(String configId, ConfigBuilder.ConfigBuilderFunction builder);
ConfigInstance register(ConfigHolder config, String configId, ConfigBuilder.ConfigBuilderFunction builder);
interface Defaulted extends DSL {
ConfigInstance config(ConfigBuilder.ConfigBuilderFunction builder);
ConfigInstance register(ConfigBuilder.ConfigBuilderFunction builder);
ConfigInstance register(ConfigHolder config, ConfigBuilder.ConfigBuilderFunction builder);
}
}

View File

@ -0,0 +1,177 @@
package io.gitlab.jfronny.libjf.config.api.type;
import org.jetbrains.annotations.Nullable;
public sealed interface Type<T> {
static Type<?> ofClass(java.lang.reflect.Type klazz) {
if (klazz == int.class || klazz == Integer.class) return TInt.INSTANCE;
else if (klazz == float.class || klazz == Float.class) return TFloat.INSTANCE;
else if (klazz == double.class || klazz == Double.class) return TDouble.INSTANCE;
else if (klazz == String.class) return TString.INSTANCE;
else if (klazz == boolean.class || klazz == Boolean.class) return TBool.INSTANCE;
else if (klazz instanceof Class<?> k && k.isEnum()) return new TEnum<>(k);
else return new TUnknown<>(klazz);
}
default boolean isInt() {
return false;
}
default boolean isFloat() {
return false;
}
default boolean isDouble() {
return false;
}
default boolean isString() {
return false;
}
default boolean isBool() {
return false;
}
default boolean isEnum() {
return false;
}
@Nullable java.lang.reflect.Type asClass();
String getName();
final class TInt implements Type<Integer> {
public static TInt INSTANCE = new TInt();
private TInt() {}
@Override
public boolean isInt() {
return true;
}
@Override
public Class<Integer> asClass() {
return Integer.class;
}
@Override
public String getName() {
return "Integer";
}
}
final class TFloat implements Type<Float> {
public static TFloat INSTANCE = new TFloat();
private TFloat() {}
@Override
public boolean isFloat() {
return true;
}
@Override
public Class<Float> asClass() {
return Float.class;
}
@Override
public String getName() {
return "Float";
}
}
final class TDouble implements Type<Double> {
public static TDouble INSTANCE = new TDouble();
private TDouble() {}
@Override
public boolean isDouble() {
return true;
}
@Override
public Class<Double> asClass() {
return Double.class;
}
@Override
public String getName() {
return "Double";
}
}
final class TString implements Type<String> {
public static TString INSTANCE = new TString();
private TString() {}
@Override
public boolean isString() {
return true;
}
@Override
public Class<String> asClass() {
return String.class;
}
@Override
public String getName() {
return "String";
}
}
final class TBool implements Type<Boolean> {
public static TBool INSTANCE = new TBool();
private TBool() {}
@Override
public boolean isBool() {
return true;
}
@Override
public Class<Boolean> asClass() {
return Boolean.class;
}
@Override
public String getName() {
return "Boolean";
}
}
final record TEnum<T>(@Nullable Class<T> klazz, String name, T[] options) implements Type<T> {
public TEnum(Class<T> klazz) {
this(klazz, klazz.getSimpleName(), klazz.getEnumConstants());
}
public static TEnum<String> create(String name, String[] options) {
return new TEnum<>(null, name, options);
}
@Override
public boolean isEnum() {
return true;
}
@Override
public Class<T> asClass() {
return klazz;
}
@Override
public String getName() {
return name;
}
public T optionForString(String string) {
for (T option : options) {
if (option.toString().equals(string)) return option;
}
return null;
}
}
final record TUnknown<T>(java.lang.reflect.Type klazz) implements Type<T> {
@Override
public @Nullable java.lang.reflect.Type asClass() {
return klazz;
}
@Override
public String getName() {
return klazz instanceof Class<?> k ? k.getSimpleName() : klazz.getTypeName();
}
}
}

View File

@ -0,0 +1,56 @@
package io.gitlab.jfronny.libjf.config.impl;
import com.google.common.collect.ImmutableMap;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import org.jetbrains.annotations.ApiStatus;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
public class ConfigHolderImpl implements ConfigHolder {
@ApiStatus.Internal
public static final ConfigHolderImpl INSTANCE = new ConfigHolderImpl();
private ConfigHolderImpl() {
}
public static final String MODULE_ID = "libjf:config";
public static final String CUSTOM_ID = MODULE_ID + "_custom";
private final Map<String, ConfigInstance> configs = new HashMap<>();
private final Map<Path, ConfigInstance> configsByPath = new HashMap<>();
@Override
public void register(String modId, ConfigInstance config) {
if (isRegistered(modId)) {
LibJf.LOGGER.warn("Overriding config class of " + modId + " to " + config);
}
configs.put(modId, config);
config.getFilePath().ifPresent(path -> configsByPath.put(path, config));
}
@Override
public Map<String, ConfigInstance> getRegistered() {
return ImmutableMap.copyOf(configs);
}
@Override
public ConfigInstance get(String configClass) {
return configs.get(configClass);
}
@Override
public ConfigInstance get(Path configPath) {
return configsByPath.get(configPath);
}
@Override
public boolean isRegistered(String modId) {
return configs.containsKey(modId);
}
@Override
public boolean isRegistered(Path configPath) {
return configsByPath.containsKey(configPath);
}
}

View File

@ -8,9 +8,8 @@ import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.api.type.Type;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.server.command.ServerCommandSource;
@ -30,7 +29,7 @@ public class JfConfigCommand implements ModInitializer {
LiteralArgumentBuilder<ServerCommandSource> c_config = literal("config")
.requires((serverCommandSource) -> serverCommandSource.hasPermissionLevel(4))
.executes(context -> {
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] Loaded configs for:"), false);
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Loaded configs for:"), false);
ConfigHolder.getInstance().getRegistered().forEach((s, config) -> {
context.getSource().sendFeedback(Text.literal("- " + s), false);
});
@ -38,15 +37,20 @@ public class JfConfigCommand implements ModInitializer {
});
LiteralArgumentBuilder<ServerCommandSource> c_reload = literal("reload").executes(context -> {
ConfigHolder.getInstance().getRegistered().forEach((mod, config) -> config.load());
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] Reloaded configs"), true);
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Reloaded configs"), true);
return Command.SINGLE_SUCCESS;
});
LiteralArgumentBuilder<ServerCommandSource> c_reset = literal("reset").executes(context -> {
context.getSource().sendError(Text.literal("[libjf-config-v0] Please specify a config to reset"));
context.getSource().sendError(Text.literal("[libjf-config-v1] Please specify a config to reset"));
return Command.SINGLE_SUCCESS;
});
ConfigHolder.getInstance().getRegistered().forEach((id, config) -> {
registerEntries(config, id, c_config, c_reload, c_reset, cns -> {
c_reload.then(literal(id).executes(context -> {
config.load();
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Reloaded config for " + id), true);
return Command.SINGLE_SUCCESS;
}));
registerEntries(config, id, c_config, c_reset, cns -> {
LiteralArgumentBuilder<ServerCommandSource> c_instance = literal(id);
cns.accept(c_instance);
return c_instance;
@ -56,37 +60,32 @@ public class JfConfigCommand implements ModInitializer {
});
}
private void registerEntries(ConfigInstance config, String subpath, LiteralArgumentBuilder<ServerCommandSource> c_config, LiteralArgumentBuilder<ServerCommandSource> c_reload, LiteralArgumentBuilder<ServerCommandSource> c_reset, Function<Consumer<LiteralArgumentBuilder<ServerCommandSource>>, LiteralArgumentBuilder<ServerCommandSource>> pathGen) {
private void registerEntries(ConfigCategory config, String subpath, LiteralArgumentBuilder<ServerCommandSource> c_config, LiteralArgumentBuilder<ServerCommandSource> c_reset, Function<Consumer<LiteralArgumentBuilder<ServerCommandSource>>, LiteralArgumentBuilder<ServerCommandSource>> pathGen) {
c_config.then(pathGen.apply(cns -> {
cns.executes(context -> {
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] " + subpath + " is a category"), false);
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] " + subpath + " is a category"), false);
return Command.SINGLE_SUCCESS;
});
for (EntryInfo<?> entry : config.getEntries()) {
registerEntry(config, subpath, cns, entry);
}
}));
c_reload.then(pathGen.apply(cns -> cns.executes(context -> {
config.load();
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] Reloaded config for " + subpath), true);
return Command.SINGLE_SUCCESS;
})));
c_reset.then(pathGen.apply(cns -> {
cns.executes(context -> {
config.getPresets().get(ConfigInstanceAbstract.CONFIG_PRESET_DEFAULT).run();
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] Reset config for " + subpath), true);
config.reset();
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Reset config for " + subpath), true);
return Command.SINGLE_SUCCESS;
});
config.getPresets().forEach((id2, preset) -> {
cns.then(literal(id2).executes(context -> {
preset.run();
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] Loaded preset " + id2 + " for " + subpath), true);
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Loaded preset " + id2 + " for " + subpath), true);
return Command.SINGLE_SUCCESS;
}));
});
}));
config.getCategories().forEach((id2, cfg) -> {
registerEntries(cfg, cfg.getCategoryPath(), c_config, c_reload, c_reset, cns -> {
registerEntries(cfg, cfg.getCategoryPath(), c_config, c_reset, cns -> {
return pathGen.apply(cns1 -> {
LiteralArgumentBuilder<ServerCommandSource> c_instance2 = literal(id2);
cns.accept(c_instance2);
@ -102,25 +101,25 @@ public class JfConfigCommand implements ModInitializer {
} else return Text.literal("Could not execute command");
});
private <T> void registerEntry(ConfigInstance config, String subpath, LiteralArgumentBuilder<ServerCommandSource> cns, EntryInfo<T> entry) {
private <T> void registerEntry(ConfigCategory config, String subpath, LiteralArgumentBuilder<ServerCommandSource> cns, EntryInfo<T> entry) {
LiteralArgumentBuilder<ServerCommandSource> c_entry = literal(entry.getName()).executes(context -> {
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] The value of " + subpath + "." + entry.getName() + " is " + tryRun(entry::getValue)), false);
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] The value of " + subpath + "." + entry.getName() + " is " + tryRun(entry::getValue)), false);
return Command.SINGLE_SUCCESS;
});
ArgumentType<?> type = getType(entry);
if (type != null) {
c_entry.then(argument("value", type).executes(context -> {
T value = context.getArgument("value", entry.getValueType());
@SuppressWarnings("unchecked") T value = context.getArgument("value", (Class<T>) entry.getValueType().asClass());
tryRun(() -> entry.setValue(value));
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] Set " + subpath + "." + entry.getName() + " to " + value), true);
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Set " + subpath + "." + entry.getName() + " to " + value), true);
return Command.SINGLE_SUCCESS;
}));
}
else if (entry.getValueType().isEnum()) {
for (T enumConstant : entry.getValueType().getEnumConstants()) {
for (T enumConstant : ((Type.TEnum<T>)entry.getValueType()).options()) {
c_entry.then(literal(enumConstant.toString()).executes(context -> {
tryRun(() -> entry.setValue(enumConstant));
context.getSource().sendFeedback(Text.literal("[libjf-config-v0] Set " + subpath + "." + entry.getName() + " to " + enumConstant), true);
context.getSource().sendFeedback(Text.literal("[libjf-config-v1] Set " + subpath + "." + entry.getName() + " to " + enumConstant), true);
return Command.SINGLE_SUCCESS;
}));
}
@ -129,12 +128,12 @@ public class JfConfigCommand implements ModInitializer {
}
private <T> ArgumentType<?> getType(EntryInfo<T> info) {
Class<T> type = info.getValueType();
if (type == int.class || type == Integer.class) return IntegerArgumentType.integer((int) info.getMinValue(), (int) info.getMaxValue());
else if (type == float.class || type == Float.class) return FloatArgumentType.floatArg((float) info.getMinValue(), (float) info.getMaxValue());
else if (type == double.class || type == Double.class) return DoubleArgumentType.doubleArg(info.getMinValue(), info.getMaxValue());
else if (type == String.class) return StringArgumentType.greedyString();
else if (type == boolean.class || type == Boolean.class) return BoolArgumentType.bool();
Type<T> type = info.getValueType();
if (type.isInt()) return IntegerArgumentType.integer((int) info.getMinValue(), (int) info.getMaxValue());
else if (type.isFloat()) return FloatArgumentType.floatArg((float) info.getMinValue(), (float) info.getMaxValue());
else if (type.isDouble()) return DoubleArgumentType.doubleArg(info.getMinValue(), info.getMaxValue());
else if (type.isString()) return StringArgumentType.greedyString();
else if (type.isBool()) return BoolArgumentType.bool();
else return null;
}

View File

@ -0,0 +1,166 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.libjf.config.api.*;
import io.gitlab.jfronny.libjf.config.api.dsl.CategoryBuilder;
import io.gitlab.jfronny.libjf.config.api.type.Type;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class CategoryBuilderImpl<Builder extends CategoryBuilderImpl<Builder>> implements CategoryBuilder<Builder> {
public final List<CategoryBuilder<?>> categories = new LinkedList<>();
public final String id;
public final String categoryPath;
public String translationPrefix;
public final List<EntryInfo<?>> entries = new LinkedList<>();
public final Map<String, Consumer<ConfigCategory>> presets = new LinkedHashMap<>();
public final List<Supplier<ConfigInstance>> referencedConfigs = new LinkedList<>();
public final List<Consumer<ConfigCategory>> verifiers = new LinkedList<>();
private boolean built = false;
public CategoryBuilderImpl(String id, String categoryPath) {
this.id = id;
this.categoryPath = categoryPath;
this.translationPrefix = id + ".jfconfig." + categoryPath;
}
@Override
public Builder setTranslationPrefix(String translationPrefix) {
checkBuilt();
this.translationPrefix = translationPrefix;
return asBuilder();
}
@Override
public String getTranslationPrefix() {
checkBuilt();
return translationPrefix;
}
@Override
public Builder addPreset(String id, Consumer<ConfigCategory> action) {
checkBuilt();
presets.put(id, action);
return asBuilder();
}
@Override
public Builder addVerifier(Consumer<ConfigCategory> verifier) {
checkBuilt();
verifiers.add(verifier);
return asBuilder();
}
@Override
public Builder referenceConfig(String id) {
checkBuilt();
referencedConfigs.add(() -> ConfigHolder.getInstance().get(id));
return asBuilder();
}
@Override
public Builder referenceConfig(ConfigInstance config) {
checkBuilt();
referencedConfigs.add(() -> config);
return asBuilder();
}
@Override
public Builder category(String id, CategoryBuilderFunction builder) {
checkBuilt();
categories.add(builder.apply(new CategoryBuilderImpl(id, categoryPath + id + ".")));
return asBuilder();
}
@Override
public Builder value(String id, int def, double min, double max, Supplier<Integer> get, Consumer<Integer> set) {
checkBuilt();
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, Type.TInt.INSTANCE, 100, min, max));
return asBuilder();
}
@Override
public Builder value(String id, float def, double min, double max, Supplier<Float> get, Consumer<Float> set) {
checkBuilt();
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, Type.TFloat.INSTANCE, 100, min, max));
return asBuilder();
}
@Override
public Builder value(String id, double def, double min, double max, Supplier<Double> get, Consumer<Double> set) {
checkBuilt();
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, Type.TDouble.INSTANCE, 100, min, max));
return asBuilder();
}
@Override
public Builder value(String id, String def, Supplier<String> get, Consumer<String> set) {
checkBuilt();
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, Type.TString.INSTANCE));
return asBuilder();
}
@Override
public Builder value(String id, boolean def, Supplier<Boolean> get, Consumer<Boolean> set) {
checkBuilt();
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, Type.TBool.INSTANCE));
return asBuilder();
}
@Override
public Builder value(String id, String def, String[] options, Supplier<String> get, Consumer<String> set) {
checkBuilt();
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, Type.TEnum.create(id, options)));
return asBuilder();
}
@Override
public <T extends Enum<T>> Builder value(String id, T def, Class<T> klazz, Supplier<T> get, Consumer<T> set) {
checkBuilt();
entries.add(new DslEntryInfo<>(id, def, get::get, set::accept, new Type.TEnum<>(klazz)));
return asBuilder();
}
@Override
public <T> Builder value(EntryInfo<T> entry) {
checkBuilt();
entries.add(Objects.requireNonNull(entry));
return asBuilder();
}
@Override
public String getId() {
return id;
}
protected Builder asBuilder() {
//noinspection unchecked
return (Builder) this;
}
protected void checkBuilt() {
if (built) throw new IllegalStateException("This builder was already used to build a category!");
}
protected void markBuilt() {
checkBuilt();
built = true;
}
@Override
public DslConfigCategory build(Supplier<ConfigInstance> root) {
markBuilt();
return new DslConfigCategory(id,
categoryPath,
translationPrefix,
entries,
presets,
() -> referencedConfigs.stream().map(Supplier::get).toList(),
categories.stream().collect(Collectors.toMap(CategoryBuilder::getId, b -> b.build(root))),
root,
verifiers);
}
}

View File

@ -0,0 +1,58 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.dsl.CategoryBuilder;
import io.gitlab.jfronny.libjf.config.api.dsl.ConfigBuilder;
import java.nio.file.Path;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ConfigBuilderImpl extends CategoryBuilderImpl<ConfigBuilderImpl> implements ConfigBuilder<ConfigBuilderImpl> {
public DslConfigInstance built;
public Consumer<ConfigInstance> load = s -> {};
public Consumer<ConfigInstance> write = s -> {};
public Path path;
public ConfigBuilderImpl(String id) {
super(id, "");
}
@Override
public ConfigBuilderImpl setLoadMethod(Consumer<ConfigInstance> load) {
checkBuilt();
this.load = load;
return this;
}
public ConfigBuilderImpl setWriteMethod(Consumer<ConfigInstance> write) {
checkBuilt();
this.write = write;
return this;
}
public ConfigBuilderImpl setPath(Path path) {
checkBuilt();
this.path = path;
return this;
}
@Override
public DslConfigInstance build() {
markBuilt();
built = new DslConfigInstance(id,
translationPrefix,
entries,
presets,
() -> referencedConfigs.stream().map(Supplier::get).toList(),
categories.stream().collect(Collectors.toMap(CategoryBuilder::getId, b -> b.build(() -> built))),
() -> built,
verifiers,
load,
write,
path);
built.load();
return built;
}
}

View File

@ -0,0 +1,48 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.dsl.ConfigBuilder;
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
public class DSLImpl implements DSL {
@Override
public ConfigInstance config(String configId, ConfigBuilder.ConfigBuilderFunction builder) {
return builder.apply(new ConfigBuilderImpl(configId)).build();
}
@Override
public ConfigInstance register(String configId, ConfigBuilder.ConfigBuilderFunction builder) {
return register(ConfigHolder.getInstance(), configId, builder);
}
@Override
public ConfigInstance register(ConfigHolder config, String configId, ConfigBuilder.ConfigBuilderFunction builder) {
ConfigInstance instance = config(configId, builder);
config.register(configId, instance);
return instance;
}
public static class Defaulted extends DSLImpl implements DSL.Defaulted {
public final String defaultId;
public Defaulted(String defaultId) {
this.defaultId = defaultId;
}
@Override
public ConfigInstance config(ConfigBuilder.ConfigBuilderFunction builder) {
return config(defaultId, builder);
}
@Override
public ConfigInstance register(ConfigBuilder.ConfigBuilderFunction builder) {
return register(defaultId, builder);
}
@Override
public ConfigInstance register(ConfigHolder config, ConfigBuilder.ConfigBuilderFunction builder) {
return register(config, defaultId, builder);
}
}
}

View File

@ -0,0 +1,89 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.libjf.config.api.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class DslConfigCategory implements ConfigCategory {
private final String id;
private final String categoryPath;
private final String translationPrefix;
private final List<EntryInfo<?>> entries;
private final Map<String, Consumer<ConfigCategory>> presets;
private final Supplier<List<ConfigInstance>> referencedConfigs;
private final Map<String, ConfigCategory> categories;
private final Supplier<ConfigInstance> root;
private final List<Consumer<ConfigCategory>> verifiers;
public DslConfigCategory(String id,
String categoryPath,
String translationPrefix,
List<EntryInfo<?>> entries,
Map<String, Consumer<ConfigCategory>> presets,
Supplier<List<ConfigInstance>> referencedConfigs,
Map<String, ConfigCategory> categories,
Supplier<ConfigInstance> root,
List<Consumer<ConfigCategory>> verifiers) {
this.id = id;
this.categoryPath = categoryPath;
this.translationPrefix = translationPrefix;
this.entries = entries;
this.presets = presets;
this.referencedConfigs = referencedConfigs;
this.categories = categories;
this.root = root;
this.verifiers = verifiers;
}
@Override
public String getId() {
return id;
}
@Override
public String getCategoryPath() {
return categoryPath;
}
@Override
public String getTranslationPrefix() {
return translationPrefix;
}
@Override
public List<EntryInfo<?>> getEntries() {
return entries;
}
@Override
public Map<String, Runnable> getPresets() {
return presets.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey,
t -> () -> t.getValue().accept(this)
));
}
@Override
public List<ConfigInstance> getReferencedConfigs() {
return referencedConfigs.get();
}
@Override
public Map<String, ConfigCategory> getCategories() {
return categories;
}
@Override
public ConfigInstance getRoot() {
return root.get();
}
@Override
public void fix() {
ConfigCategory.super.fix();
for (Consumer<ConfigCategory> verifier : verifiers) verifier.accept(this);
}
}

View File

@ -0,0 +1,47 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.libjf.config.api.*;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class DslConfigInstance extends DslConfigCategory implements ConfigInstance {
private final Consumer<ConfigInstance> load;
private final Consumer<ConfigInstance> write;
private final Path path;
public DslConfigInstance(String id,
String translationPrefix,
List<EntryInfo<?>> entries,
Map<String, Consumer<ConfigCategory>> presets,
Supplier<List<ConfigInstance>> referencedConfigs,
Map<String, ConfigCategory> categories,
Supplier<ConfigInstance> root,
List<Consumer<ConfigCategory>> verifiers,
Consumer<ConfigInstance> load,
Consumer<ConfigInstance> write,
@Nullable Path path) {
super(id, "", translationPrefix, entries, presets, referencedConfigs, categories, root, verifiers);
this.load = load;
this.write = write;
this.path = path;
}
@Override
public void load() {
load.accept(this);
}
@Override
public void write() {
write.accept(this);
}
@Override
public Optional<Path> getFilePath() {
return Optional.ofNullable(path);
}
}

View File

@ -0,0 +1,174 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.type.Type;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;
public class DslEntryInfo<T> implements EntryInfo<T> {
private final String name;
private final T defaultValue;
private final ThrowingSupplier<T, IllegalAccessException> get;
private final ThrowingConsumer<T, IllegalAccessException> set;
private final Type<T> type;
private final int width;
private final double minValue;
private final double maxValue;
public DslEntryInfo(String name,
T defaultValue,
ThrowingSupplier<T, IllegalAccessException> get,
ThrowingConsumer<T, IllegalAccessException> set,
Type<T> type,
int width,
double minValue,
double maxValue) {
this.name = name;
this.defaultValue = defaultValue;
this.get = get;
this.set = set;
this.type = type;
this.width = width;
this.minValue = minValue;
this.maxValue = maxValue;
}
public DslEntryInfo(String name,
T def,
ThrowingSupplier<T, IllegalAccessException> get,
ThrowingConsumer<T, IllegalAccessException> set,
Type<T> type) {
this(name, def, get, set, type, 100, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
}
@Override
public String getName() {
return name;
}
@Override
public T getDefault() {
return defaultValue;
}
@Override
public T getValue() throws IllegalAccessException {
return get.get();
}
@Override
public void setValue(T value) throws IllegalAccessException {
set.accept(value);
}
@Override
public Type<T> getValueType() {
return type;
}
@Override
public void fix() {
Object value;
try {
value = getValue();
} catch (IllegalAccessException e) {
LibJf.LOGGER.error("Could not read value", e);
return;
}
final Object valueOriginal = value;
if (value instanceof final Integer v) {
if (v < minValue) value = minValue;
if (v > maxValue) value = maxValue;
} else if (value instanceof final Float v) {
if (v < minValue) value = minValue;
if (v > maxValue) value = maxValue;
} else if (value instanceof final Double v) {
if (v < minValue) value = minValue;
if (v > maxValue) value = maxValue;
}
if (valueOriginal != value) {
try {
setUnchecked(value);
} catch (IllegalAccessException e) {
LibJf.LOGGER.error("Could not write value", e);
}
}
}
@Override
public void loadFromJson(JsonElement element) throws IllegalAccessException {
if (type.isBool()) {
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean()) {
setUnchecked(element.getAsBoolean());
}
} else if (type.isString()) {
if (element.isJsonPrimitive()) {
setUnchecked(element.getAsString());
}
} else if (type.isInt()) {
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
setUnchecked(element.getAsNumber().intValue());
}
} else if (type.isDouble()) {
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
setUnchecked(element.getAsNumber().doubleValue());
}
} else if (type.isFloat()) {
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) {
setUnchecked(element.getAsNumber().floatValue());
}
} else if (type.isEnum()) {
Type.TEnum<T> e = (Type.TEnum<T>) type;
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) {
setUnchecked(e.optionForString(element.getAsString()));
}
} else {
setValue(GsonHolder.getGson().fromJson(element, type.asClass()));
}
}
@SuppressWarnings("unchecked")
private void setUnchecked(Object object) throws IllegalAccessException {
if (object == null) return;
setValue((T) object);
}
@Override
public void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException {
T value = getValue();
String val;
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(translationPrefix + getName() + ".tooltip")) != null) {
writer.comment(val);
}
if (type.isEnum()) {
writer.comment("Valid: [" + Arrays.stream(((Type.TEnum<T>)type).options()).map(Objects::toString).collect(Collectors.joining(", ")) + "]");
}
writer.name(name);
GsonHolder.getGson().toJson(value, Objects.requireNonNullElse(type.asClass(), String.class), writer);
}
@Override
public int getWidth() {
return width;
}
@Override
public double getMinValue() {
return minValue;
}
@Override
public double getMaxValue() {
return maxValue;
}
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.libjf.config.impl.entrypoint;
import io.gitlab.jfronny.libjf.config.api.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
import net.minecraft.util.Language;
import java.util.function.Function;
public class JfConfigSafe implements PreLaunchEntrypoint {
public static Function<String, String> TRANSLATION_SUPPLIER = s -> null;
@Override
public void onPreLaunch() {
for (EntrypointContainer<JfCustomConfig> custom : FabricLoader.getInstance().getEntrypointContainers(ConfigHolderImpl.CUSTOM_ID, JfCustomConfig.class)) {
custom.getEntrypoint().register(DSL.create(custom.getProvider().getMetadata().getId()));
}
TRANSLATION_SUPPLIER = s -> {
String translated = Language.getInstance().get(s);
return translated.equals(s) ? null : translated;
};
}
}

View File

@ -0,0 +1,6 @@
{
"libjf-config-v1.presets": "Presets",
"libjf-config-v1.default": "Default",
"libjf-config-v1.see-also": "See also: %s",
"libjf-config-v1.reset": "Reset"
}

View File

@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"id": "libjf-config-v0",
"id": "libjf-config-v1",
"name": "LibJF Config",
"version": "${version}",
"authors": ["JFronny"],
@ -13,7 +13,6 @@
"entrypoints": {
"modmenu": ["io.gitlab.jfronny.libjf.config.impl.client.ModMenu"],
"client": ["io.gitlab.jfronny.libjf.config.impl.client.JfConfigClient"],
"libjf:preEarly": ["io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigUnsafe"],
"preLaunch": ["io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe"],
"main": ["io.gitlab.jfronny.libjf.config.impl.JfConfigCommand"],
"libjf:coprocess": ["io.gitlab.jfronny.libjf.config.impl.JfConfigWatchService"]

View File

@ -0,0 +1,31 @@
package io.gitlab.jfronny.libjf.config.test;
import io.gitlab.jfronny.libjf.config.api.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.dsl.DSL;
public class TestConfigCustom {
public static Integer someValue = 10;
public static String someOther = "Yes";
public static class SomeCategory {
public static Boolean someBool = true;
}
public static class LibJF_Companion implements JfCustomConfig {
@Override
public void register(DSL.Defaulted dsl) {
dsl.register(builder -> builder
.referenceConfig("libjf-config-reflect-v0-testmod")
.value("someValue", 10, -50, 50, () -> someValue, v -> someValue = v)
.value("someOther", "Yes", () -> someOther, v -> someOther = v)
.category("SomeCategory", builder1 -> builder1
.value("someBool", true, () -> SomeCategory.someBool, v -> SomeCategory.someBool = v)
)
);
}
}
static {
new LibJF_Companion().register(DSL.create("libjf-config-v1-testmod"));
}
}

View File

@ -0,0 +1,18 @@
{
"schemaVersion": 1,
"id": "libjf-config-v1-testmod",
"version": "1.0",
"environment": "*",
"entrypoints": {
"libjf:config_custom": [
"io.gitlab.jfronny.libjf.config.test.TestConfigCustom$LibJF_Companion"
]
},
"custom": {
"libjf": {
"config": {
"referencedConfigs": ["libjf-web-v0"]
}
}
}
}

View File

@ -1,6 +1,7 @@
archivesBaseName = "libjf-data-manipulation-v0"
dependencies {
moduleDependencies(project, ["libjf-base", "libjf-unsafe-v0"])
api project(":libjf-base")
api project(":libjf-unsafe-v0")
modApi(fabricApi.module("fabric-api-base", "$rootProject.fabric_version"))
}

View File

@ -1,6 +1,6 @@
archivesBaseName = "libjf-data-v0"
dependencies {
moduleDependencies(project, ["libjf-base"])
include(fabricApi.module("fabric-resource-loader-v0", "${project.fabric_version}"))
api project(":libjf-base")
include fabricApi.module("fabric-resource-loader-v0", "${project.fabric_version}")
}

View File

@ -1,5 +1,5 @@
archivesBaseName = "libjf-devutil-v0"
dependencies {
moduleDependencies(project, ["libjf-base"])
api project(":libjf-base")
}

View File

@ -1,5 +1,6 @@
archivesBaseName = "libjf-translate-v1"
dependencies {
moduleDependencies(project, ["libjf-base", "libjf-config-v0"])
api project(":libjf-base")
api project(":libjf-config-v1")
}

View File

@ -1,5 +1,5 @@
archivesBaseName = "libjf-unsafe-v0"
dependencies {
moduleDependencies(project, ["libjf-base"])
api project(":libjf-base")
}

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.libjf.unsafe;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.impl.ModContainerImpl;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.impl.metadata.EntrypointMetadata;
import java.lang.reflect.InvocationTargetException;
@ -46,11 +46,11 @@ public class DynamicEntry {
private static Collection<EntrypointContainer> getEntrypointTargets(final String entrypoint) {
final List<EntrypointContainer> entrypoints = new LinkedList<>();
for (final ModContainer mod : FabricLoader.getInstance().getAllMods()) {
final List<EntrypointMetadata> modEntrypoints = ((ModContainerImpl)mod).getInfo().getEntrypoints(entrypoint);
final List<Object> modEntrypoints = getEntrypoints(mod.getMetadata(), entrypoint);
if (modEntrypoints != null) {
for (final EntrypointMetadata metadata : modEntrypoints) {
entrypoints.add(new EntrypointContainer(metadata.getValue(), mod.getMetadata().getId()));
for (final Object metadata : modEntrypoints) {
entrypoints.add(new EntrypointContainer(getValue(metadata), mod.getMetadata().getId()));
}
}
}
@ -58,6 +58,27 @@ public class DynamicEntry {
return entrypoints;
}
@SuppressWarnings("unchecked")
private static List<Object> getEntrypoints(Object metadata, String name) {
try {
var method = metadata.getClass().getMethod("getEntrypoints", String.class);
method.setAccessible(true);
return (List<Object>) method.invoke(metadata, name);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private static String getValue(Object object) {
try {
var method = object.getClass().getMethod("getValue");
method.setAccessible(true);
return (String) method.invoke(object);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
record EntrypointContainer(String entrypoint, String mod) {
}

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import io.gitlab.jfronny.libjf.Flags;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
import io.gitlab.jfronny.libjf.unsafe.inject.FabricLauncherClassUnlocker;
import io.gitlab.jfronny.libjf.unsafe.inject.KnotClassLoaderInterfaceAccessor;
@ -17,17 +18,17 @@ public class JfLanguageAdapter implements LanguageAdapter {
public native <T> T create(net.fabricmc.loader.api.ModContainer mod, String value, Class<T> type);
static {
Logger.registerFactory(FLLogger::new); // Reset in SafeLog entrypoint
Logger.registerFactory(FLLogger::new); // Reset in LibJf entrypoint
Set<Flags.BooleanFlag> flags = Flags.getBoolFlags("unsafe.unlock");
if (flags.stream().map(Flags.BooleanFlag::value).reduce(false, (left, right) -> left || right)) {
SafeLog.warn("Unlocking classpath due to: " + flags.stream().map(Flags.BooleanFlag::source).collect(Collectors.joining(", ")));
LibJf.LOGGER.warn("Unlocking classpath due to: " + flags.stream().map(Flags.BooleanFlag::source).collect(Collectors.joining(", ")));
FabricLoaderImpl.INSTANCE.getGameProvider().unlockClassPath(new FabricLauncherClassUnlocker(new KnotClassLoaderInterfaceAccessor(Thread.currentThread().getContextClassLoader())));
SafeLog.warn("Completed classpath unlock");
LibJf.LOGGER.warn("Completed classpath unlock");
}
HiddenAnnotationExclusionStrategy.register();
GsonHolder.register();
DynamicEntry.execute("libjf:preEarly", UltraEarlyInit.class, s -> s.instance().init());
DynamicEntry.execute("libjf:early", UltraEarlyInit.class, s -> s.instance().init());
SafeLog.info("LibJF unsafe init completed");
LibJf.LOGGER.info("LibJF unsafe init completed");
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
@ -28,13 +29,13 @@ public class MixinPlugin implements IMixinConfigPlugin {
AsmTransformer.INSTANCE.delegate = (IMixinTransformer) mixinTransformerField.get(delegate);
AsmTransformer.INSTANCE.asmConfigs = new HashSet<>();
DynamicEntry.execute("libjf:asm", AsmConfig.class, s -> {
SafeLog.info("Discovered LibJF asm plugin in " + s.modId());
LibJf.LOGGER.info("Discovered LibJF asm plugin in " + s.modId());
AsmTransformer.INSTANCE.asmConfigs.add(new BakedAsmConfig(s.instance(), s.modId()));
});
AsmTransformer.INSTANCE.init();
mixinTransformerField.set(delegate, AsmTransformer.INSTANCE);
} catch (Throwable e) {
SafeLog.error("Could not initialize LibJF ASM", e);
LibJf.LOGGER.error("Could not initialize LibJF ASM", e);
}
}

View File

@ -1,34 +0,0 @@
package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.commons.log.Logger;
import net.fabricmc.api.ModInitializer;
public class SafeLog implements ModInitializer {
private static Logger BACKEND = Logger.forName("LibJF");
public static void debug(String text) {
BACKEND.debug(text);
}
public static void info(String text) {
BACKEND.info(text);
}
public static void warn(String text) {
BACKEND.warn(text);
}
public static void error(String text) {
BACKEND.error(text);
}
public static void error(String text, Throwable e) {
BACKEND.error(text, e);
}
@Override
public void onInitialize() {
Logger.resetFactory();
BACKEND = Logger.forName("libjf");
}
}

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.libjf.unsafe.asm;
import io.gitlab.jfronny.libjf.Flags;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.MappingResolver;
@ -39,7 +39,7 @@ public class AsmTransformer implements IMixinTransformer {
for (Flags.BooleanFlag flag : flags) {
flagNames.add(flag.source());
}
SafeLog.info("Exporting ASM due to: " + String.join(", ", flagNames));
LibJf.LOGGER.info("Exporting ASM due to: " + String.join(", ", flagNames));
}
flags = Flags.getBoolFlags("asm.log");
flags.removeIf(flag -> !flag.value());
@ -49,7 +49,7 @@ public class AsmTransformer implements IMixinTransformer {
for (Flags.BooleanFlag flag : flags) {
flagNames.add(flag.source());
}
SafeLog.info("Logging ASM logs due to: " + String.join(", ", flagNames));
LibJf.LOGGER.info("Logging ASM logs due to: " + String.join(", ", flagNames));
}
}
@ -77,7 +77,7 @@ public class AsmTransformer implements IMixinTransformer {
classBytes = delegate.transformClassBytes(name, transformedName, classBytes);
if (classBytes == null || name == null) return classBytes;
if (isClassUnmoddable(name, null)) {
if (debugLogsEnabled()) SafeLog.info("Skipping " + name);
if (debugLogsEnabled()) LibJf.LOGGER.info("Skipping " + name);
return classBytes;
}
@ -93,7 +93,7 @@ public class AsmTransformer implements IMixinTransformer {
patch.apply(klazz);
}
catch (Throwable t) {
SafeLog.error("Could not apply patch: " + patch.getClass() + " on " + name, t);
LibJf.LOGGER.error("Could not apply patch: " + patch.getClass() + " on " + name, t);
}
}
}
@ -105,7 +105,7 @@ public class AsmTransformer implements IMixinTransformer {
klazz.accept(writer);
}
catch (NullPointerException t) {
SafeLog.error("Could not transform " + transformedName, t);
LibJf.LOGGER.error("Could not transform " + transformedName, t);
return null;
}
classBytes = writer.toByteArray();
@ -115,7 +115,7 @@ public class AsmTransformer implements IMixinTransformer {
if (!Files.exists(path)) Files.createDirectories(path.getParent());
Files.write(path, classBytes);
} catch (IOException e) {
SafeLog.error("Could not export modified bytecode", e);
LibJf.LOGGER.error("Could not export modified bytecode", e);
}
}
return classBytes;

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.modification;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.MethodPatch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
@ -22,7 +22,7 @@ public class MethodModificationPatch implements Patch {
);
}
for (String s : this.patches.keySet()) {
if (AsmTransformer.INSTANCE.debugLogsEnabled()) SafeLog.info("Registered patch for " + s);
if (AsmTransformer.INSTANCE.debugLogsEnabled()) LibJf.LOGGER.info("Registered patch for " + s);
}
}
@ -30,7 +30,7 @@ public class MethodModificationPatch implements Patch {
public void apply(ClassNode klazz) {
for (MethodNode method : klazz.methods) {
if (patches.containsKey(method.name)) {
if (AsmTransformer.INSTANCE.debugLogsEnabled()) SafeLog.info("Patching " + method.name);
if (AsmTransformer.INSTANCE.debugLogsEnabled()) LibJf.LOGGER.info("Patching " + method.name);
patches.get(method.name).apply(method, klazz);
}
}

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.targeting;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import org.objectweb.asm.Type;
@ -22,7 +22,7 @@ public class InterfaceImplTargetPatch implements Patch {
public void apply(ClassNode klazz) {
scanInterfaces(klazz);
if (getUpper(klazz.name).contains(targetInterface)) {
if (AsmTransformer.INSTANCE.debugLogsEnabled()) SafeLog.info("Found " + klazz.name + " implementing " + targetInterface);
if (AsmTransformer.INSTANCE.debugLogsEnabled()) LibJf.LOGGER.info("Found " + klazz.name + " implementing " + targetInterface);
methodPatch.apply(klazz);
}
}
@ -78,7 +78,7 @@ public class InterfaceImplTargetPatch implements Patch {
&& !className.startsWith("it/unimi/dsi/fastutil/")
//&& !className.startsWith("com/google/")
) {
if (AsmTransformer.INSTANCE.debugLogsEnabled()) SafeLog.info("Non-default class not considered for interface scanning: " + className);
if (AsmTransformer.INSTANCE.debugLogsEnabled()) LibJf.LOGGER.info("Non-default class not considered for interface scanning: " + className);
INTERFACES.put(className, Set.of());
return Set.of();
}
@ -86,7 +86,7 @@ public class InterfaceImplTargetPatch implements Patch {
scanInterfaces(Class.forName(className.replace('/', '.')));
s = INTERFACES.get(className);
} catch (ClassNotFoundException e) {
SafeLog.error("Could not get base for " + className, e);
LibJf.LOGGER.error("Could not get base for " + className, e);
return Set.of();
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf.unsafe.inject;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.*;
import net.fabricmc.api.*;
import net.fabricmc.loader.impl.launch.*;
@ -17,7 +18,7 @@ public record FabricLauncherClassUnlocker(KnotClassLoaderInterfaceAccessor class
@Override
public void addToClassPath(Path path, String... allowedPrefixes) {
SafeLog.debug("Adding " + path + " to classpath.");
LibJf.LOGGER.debug("Adding " + path + " to classpath.");
classLoader.getDelegate().setAllowedPrefixes(path, allowedPrefixes);
classLoader.getDelegate().addCodeSource(path);
@ -89,7 +90,7 @@ public record FabricLauncherClassUnlocker(KnotClassLoaderInterfaceAccessor class
}
private <T> T invalidCall() {
SafeLog.warn("Attempted invalid call in class path unlocker");
LibJf.LOGGER.warn("Attempted invalid call in class path unlocker");
throw new IllegalStateException("unlockClassPath attempted to call a method not implemented here");
}
}

View File

@ -24,9 +24,6 @@
"breaks": {
"quilt_loader": "*"
},
"entrypoints": {
"main": ["io.gitlab.jfronny.libjf.unsafe.SafeLog"]
},
"custom": {
"modmenu": {
"parent": "libjf",

View File

@ -1,12 +1,12 @@
package io.gitlab.jfronny.libjf.unsafe.test;
import io.gitlab.jfronny.libjf.unsafe.SafeLog;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.UltraEarlyInit;
public class UnsafeEntryTest implements UltraEarlyInit {
@Override
public void init() {
SafeLog.info("Successfully executed code before that should be possible\n" +
LibJf.LOGGER.info("Successfully executed code before that should be possible\n" +
"'||' '|' '|| . '||''''| '|| \n" +
" || | || .||. ... .. .... || . .... ... .. || .... ... \n" +
" || | || || ||' '' '' .|| ||''| '' .|| ||' '' || '|. | \n" +

View File

@ -15,7 +15,7 @@
"libjf": {
"asm.export": true,
"asm.log": true,
"unsafe.unlock": true
"unsafe.unlock": false
}
}
}

View File

@ -1,6 +1,7 @@
archivesBaseName = "libjf-web-v0"
dependencies {
moduleDependencies(project, ["libjf-base", "libjf-config-v0"])
api project(":libjf-base")
api project(":libjf-config-v1")
include modImplementation(fabricApi.module("fabric-command-api-v2", "${rootProject.fabric_version}"))
}

View File

@ -21,7 +21,7 @@
"fabricloader": ">=0.12.0",
"minecraft": "*",
"libjf-base": ">=${version}",
"libjf-config-v0": ">=${version}",
"libjf-config-v1": ">=${version}",
"fabric-command-api-v2": "*"
},
"custom": {

View File

@ -12,10 +12,11 @@ rootProject.name = "libjf"
include 'libjf-base'
include 'libjf-config-v0'
include 'libjf-config-v1'
include 'libjf-config-reflect-v0'
include 'libjf-data-v0'
include 'libjf-data-manipulation-v0'
include 'libjf-devutil-v0'
include 'libjf-translate-v1'
include 'libjf-unsafe-v0'
include 'libjf-web-v0'
include 'libjf-web-v0'