[config] Implement config DSL and rewrite reflection implementation
This commit is contained in:
parent
bbe4213a68
commit
1ce7ddaf38
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
archivesBaseName = "libjf-config-reflect-v0"
|
||||
|
||||
dependencies {
|
||||
api project(":libjf-base")
|
||||
api project(":libjf-unsafe-v0")
|
||||
api project(":libjf-config-v1")
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-v0-testmod",
|
||||
"id": "libjf-config-reflect-v0-testmod",
|
||||
"version": "1.0",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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")
|
|
@ -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 -> {
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"]
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
archivesBaseName = "libjf-devutil-v0"
|
||||
|
||||
dependencies {
|
||||
moduleDependencies(project, ["libjf-base"])
|
||||
api project(":libjf-base")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
archivesBaseName = "libjf-unsafe-v0"
|
||||
|
||||
dependencies {
|
||||
moduleDependencies(project, ["libjf-base"])
|
||||
api project(":libjf-base")
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,9 +24,6 @@
|
|||
"breaks": {
|
||||
"quilt_loader": "*"
|
||||
},
|
||||
"entrypoints": {
|
||||
"main": ["io.gitlab.jfronny.libjf.unsafe.SafeLog"]
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
"parent": "libjf",
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"libjf": {
|
||||
"asm.export": true,
|
||||
"asm.log": true,
|
||||
"unsafe.unlock": true
|
||||
"unsafe.unlock": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}"))
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue