Compare commits

...

38 Commits

Author SHA1 Message Date
Johannes Frohnmeyer 5b1fbcbb76
style: make variant names common
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-05-11 21:18:58 +02:00
Johannes Frohnmeyer 03ffeb9eee
chore(translate): comment out workaround again since google might have just been flaky
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-05-11 21:08:00 +02:00
Johannes Frohnmeyer 5279dcb759
chore(translate): more text for workaround
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-05-11 19:04:00 +02:00
Johannes Frohnmeyer b47bc6fd0a
fix(translate): strange workaround 2024-05-11 18:32:39 +02:00
Johannes Frohnmeyer b4f1ca22f5
fix(translate): correct url in testmod 2024-05-11 17:53:28 +02:00
Johannes Frohnmeyer 3bc7df7089
fix(translate): do not pretend there is a page source if the connection fails
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-05-11 17:36:04 +02:00
Johannes Frohnmeyer fe2808cf45
fix(config-core): write comments and name in writeTo, not serializeOneTo
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-05-10 09:05:32 +02:00
Johannes Frohnmeyer dddda8b92a
chore(config-core): lift up logic for loadFromJson and writeTo 2024-05-10 09:03:39 +02:00
Johannes Frohnmeyer 78bcde1b86
chore(config-core): lift up logic for serializeOneTo 2024-05-10 09:01:32 +02:00
Johannes Frohnmeyer 41e8a71d28
style: enhance readability of SLF4J logger backend by removing varargs
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-05-05 14:30:45 +02:00
Johannes Frohnmeyer 34f552d4f5
fix: enhance commons logging backend with new upstream functionality
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline failed Details
2024-05-05 14:28:00 +02:00
Johannes Frohnmeyer d30c36b1c6
chore: update to 1.20.6
ci/woodpecker/tag/jfmod Pipeline is pending Details
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline failed Details
ci/woodpecker/tag/docs Pipeline failed Details
2024-05-05 14:17:50 +02:00
Johannes Frohnmeyer 41364257fb
fix: add additional null check to hook and update commons
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline is pending Details
ci/woodpecker/tag/docs Pipeline failed Details
2024-05-05 13:09:02 +02:00
Johannes Frohnmeyer 83d85abb17
fix: actually skip unsupported values in configs
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-05-04 19:56:42 +02:00
Johannes Frohnmeyer d579c19f6b
chore(config-core): plumb TypeTokens throughout internal representation
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-04-26 09:11:28 +02:00
Johannes Frohnmeyer 64f0f38668
fix(config-ui-tiny): use correct Text for default preset
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-25 19:38:45 +02:00
Johannes Frohnmeyer 8cfa3c2a93
chore(config-core): use method reference for migration
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-04-25 15:10:15 +02:00
Johannes Frohnmeyer 61d622aeb1
chore(config-core): add unsafe wrapper for generating migrations since generics apparently are too much for javac
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-04-25 12:15:10 +02:00
Johannes Frohnmeyer 08891229a9
chore(config-compiler-plugin): clean up a bit
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-04-25 11:52:13 +02:00
Johannes Frohnmeyer 4bcbc2b588
fix(config-core): use proper exception type in Migration 2024-04-25 11:51:58 +02:00
Johannes Frohnmeyer 2f54fb3921
build: temporarily remove repos needed for arch loom to ensure build uses fabric loom
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-04-25 10:24:01 +02:00
Johannes Frohnmeyer 00929dd17a
chore(config-network): align packet serialization with recommendations in blog post
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-25 09:53:04 +02:00
Johannes Frohnmeyer 1109f9e610
build: publish bom/catalog on release 2024-04-25 09:15:13 +02:00
Johannes Frohnmeyer a33b27e57d
feat: add lenient transport as alternative to manually setting SerializationMode
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-04-24 17:53:10 +02:00
Johannes Frohnmeyer bd30200197
Merge branch 'refs/heads/breaking'
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-23 21:06:21 +02:00
Johannes Frohnmeyer 76992eaab0
feat: update to 1.20.5
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
ci/woodpecker/tag/docs Pipeline was successful Details
ci/woodpecker/tag/jfmod Pipeline was successful Details
2024-04-23 20:54:30 +02:00
Johannes Frohnmeyer 799a49a065
style(data-manipulation): Use ScopedValue instead of map from thread ID to value 2024-04-23 20:54:08 +02:00
Johannes Frohnmeyer 7a75c776e4
feat: update to 1.20.5-rc2
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-19 20:09:09 +02:00
Johannes Frohnmeyer f88996e8f3
fix: consistently use SerializationMode
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-19 07:43:09 +02:00
Johannes Frohnmeyer cb1821853b
feat: port to commons 1.7
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-18 23:09:35 +02:00
Johannes Frohnmeyer 38ea9f1f44
build: remove unneeded isFabric check
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-10 16:56:57 +02:00
Johannes Frohnmeyer 5d341b3d4b
fix: actually publish bom and catalog
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-04-05 15:24:10 +02:00
Johannes Frohnmeyer aaa71cfa42
build: update build for changes in scripts
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-03-25 16:16:25 +01:00
Johannes Frohnmeyer c98c713da8
chore: add comments explaining the logging system
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-03-15 11:54:41 +01:00
Johannes Frohnmeyer 3c691ffe86
fix(base): Separate bootstrap stage with fallback JPL->JUL->our JPL logger redirection
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-03-15 11:43:44 +01:00
Johannes Frohnmeyer 7080cfbff2
feat: switch loggers to System.Logger and update dependencies
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-03-14 16:01:39 +01:00
Johannes Frohnmeyer 580a74fee8
fix(config-core): hopefully prevent config parsing issue with nulls
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-03-12 14:16:59 +01:00
Johannes Frohnmeyer 45cbc38d68
build: generate bom and catalog
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details
2024-03-08 18:13:37 +01:00
72 changed files with 1086 additions and 557 deletions

View File

@ -5,20 +5,11 @@ plugins {
allprojects { group = "io.gitlab.jfronny.libjf" }
subprojects { version = rootProject.version }
val fabricVersion by extra("0.91.1+1.20.4")
val commonsVersion by extra("1.5-SNAPSHOT")
val gsonCompileVersion by extra("1.4-SNAPSHOT")
val modmenuVersion by extra("9.0.0-pre.1")
val annotationsVersion by extra("24.0.1")
val javapoetVersion by extra("1.13.0")
val baseCommonsModules by extra(listOf("http-client", "io", "logging", "logging-slf4j", "serialize", "serialize-gson"))
jfMod {
minecraftVersion = "1.20.4"
yarn("build.1")
loaderVersion = "0.15.0"
minecraftVersion = libs.versions.minecraft
yarn(libs.versions.yarn.get())
loaderVersion = libs.versions.fabric.loader
fabricApiVersion = libs.versions.fabric.api
modrinth {
projectId = "libjf"
@ -30,20 +21,21 @@ jfMod {
}
}
base {
archivesName = "libjf"
}
allprojects {
if (!rootProject.jfMod.isMod(this)) return@allprojects
base {
archivesName = "libjf"
}
dependencies {
modLocalRuntime("com.terraformersmc:modmenu:$modmenuVersion") {
exclude("net.fabricmc") // required to work around duplicate fabric loaders
}
modLocalRuntime(fabricApi.module("fabric-command-api-v2", fabricVersion))
modLocalRuntime(fabricApi.module("fabric-networking-api-v1", fabricVersion))
compileOnly("io.gitlab.jfronny:commons:$commonsVersion")
baseCommonsModules.forEach { compileOnly("io.gitlab.jfronny:commons-$it:$commonsVersion") }
// modLocalRuntime(libs.modmenu) {
// exclude("net.fabricmc") // required to work around duplicate fabric loaders
// }
modLocalRuntime("net.fabricmc.fabric-api:fabric-command-api-v2")
modLocalRuntime("net.fabricmc.fabric-api:fabric-networking-api-v1")
modLocalRuntime("net.fabricmc.fabric-api:fabric-resource-loader-v0")
modLocalRuntime("net.fabricmc.fabric-api:fabric-lifecycle-events-v1")
compileOnly(libs.bundles.commons)
}
}

27
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,27 @@
# jf-scripts version is in settings.gradle.kts
[versions]
minecraft = "1.20.6"
yarn = "build.1"
fabric-loader = "0.15.11"
fabric-api = "0.97.8+1.20.6"
jf-commons = "1.7-SNAPSHOT"
modmenu = "10.0.0-beta.1"
annotations = "24.1.0"
javapoet = "1.13.0"
[libraries]
commons = { module = "io.gitlab.jfronny:commons", version.ref="jf-commons" }
commons-http-client = { module = "io.gitlab.jfronny:commons-http-client", version.ref="jf-commons" }
commons-io = { module = "io.gitlab.jfronny:commons-io", version.ref="jf-commons" }
commons-logger = { module = "io.gitlab.jfronny:commons-logger", version.ref="jf-commons" }
commons-serialize = { module = "io.gitlab.jfronny:commons-serialize", version.ref="jf-commons" }
commons-serialize-databind = { module = "io.gitlab.jfronny:commons-serialize-databind", version.ref="jf-commons" }
commons-serialize-json = { module = "io.gitlab.jfronny:commons-serialize-json", version.ref="jf-commons" }
commons-serialize-generator-core = { module = "io.gitlab.jfronny:commons-serialize-generator-core", version.ref="jf-commons" }
commons-unsafe = { module = "io.gitlab.jfronny:commons-unsafe", version.ref="jf-commons" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref="modmenu" }
annotations = { module = "org.jetbrains:annotations", version.ref="annotations" }
javapoet = { module = "com.squareup:javapoet", version.ref="javapoet" }
[bundles]
commons = ["commons", "commons-http-client", "commons-logger", "commons-io", "commons-serialize", "commons-serialize-databind", "commons-serialize-json"]

View File

@ -1,18 +1,35 @@
import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.codehaus.plexus.util.IOUtil
import java.io.ByteArrayOutputStream
plugins {
id("jfmod.module")
}
base {
archivesName.set("libjf-base")
archivesName = "libjf-base"
}
dependencies {
val fabricVersion: String by rootProject.extra
val commonsVersion: String by rootProject.extra
val baseCommonsModules: List<String> by rootProject.extra
include(modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricVersion))!!)
shadow("io.gitlab.jfronny:commons:$commonsVersion")
baseCommonsModules.forEach { shadow("io.gitlab.jfronny:commons-$it:$commonsVersion") {
if (it == "logging-slf4j") isTransitive = false
} }
include(modImplementation("net.fabricmc.fabric-api:fabric-lifecycle-events-v1")!!)
shadow(libs.bundles.commons)
}
// workaround to ensure our custom System.LoggerFinder is initialized early
// ideally, we would use the configuration API, but that seems to cause a class loading issue with fabric loader
tasks.shadowJar {
val path = "META-INF/services/java.lang.System\$LoggerFinder"
val field = ServiceFileTransformer::class.java.getDeclaredField("serviceEntries").apply { isAccessible = true }
exclude(path)
transform(object: ServiceFileTransformer() {
private val serviceEntries get() = field.get(this) as MutableMap<String, Any>
override fun transform(context: TransformerContext?) {
super.transform(context)
(serviceEntries[path] as ByteArrayOutputStream).run {
reset()
file("src/main/resources/$path").inputStream().use { IOUtil.copy(it, this) }
}
}
})
}

View File

@ -1,21 +1,28 @@
package io.gitlab.jfronny.libjf;
import io.gitlab.jfronny.commons.logging.Level;
import io.gitlab.jfronny.commons.logging.Logger;
import io.gitlab.jfronny.commons.logging.slf4j.SLF4JLogger;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
import net.fabricmc.loader.api.FabricLoader;
import com.sun.jdi.connect.Transport;
import io.gitlab.jfronny.commons.Serializer;
import io.gitlab.jfronny.commons.logger.DelegateLogger;
import io.gitlab.jfronny.commons.logger.HotswapLoggerFinder;
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
import io.gitlab.jfronny.commons.serialize.databind.DatabindSerializer;
import io.gitlab.jfronny.commons.serialize.databind.ObjectMapper;
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
import io.gitlab.jfronny.libjf.log.JULBridge;
import io.gitlab.jfronny.libjf.log.SLF4JPlatformLogger;
import io.gitlab.jfronny.libjf.serialize.LenientTransport;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
import org.slf4j.LoggerFactory;
public class LibJf implements PreLaunchEntrypoint {
public static final String MOD_ID = "libjf";
public static final Logger LOGGER = Logger.forName(MOD_ID);
public static final SystemLoggerPlus LOGGER = SystemLoggerPlus.forName(MOD_ID);
public static final ObjectMapper MAPPER = new ObjectMapper();
public static final JsonTransport JSON_TRANSPORT = new JsonTransport();
public static final JsonTransport LENIENT_TRANSPORT = new LenientTransport();
static {
HiddenAnnotationExclusionStrategy.register();
GsonHolders.registerSerializer();
Logger.setMinimumLevel(FabricLoader.getInstance().isDevelopmentEnvironment() ? Level.TRACE : Level.INFO);
Serializer.setInstance(new DatabindSerializer<>(JSON_TRANSPORT, MAPPER));
}
@Override
@ -24,10 +31,35 @@ public class LibJf implements PreLaunchEntrypoint {
}
private static boolean setup = false;
private static boolean bootstrapped = false;
public static void setup() {
bootstrap();
if (!setup) {
setup = true;
Logger.registerFactory(SLF4JLogger::new);
// Sometimes a mod using LibJF may want to log before game libraries are initialized.
// For that case, an implementation of System.Logger that redirects to Fabric Loader's logger is used at startup.
// After the game is initialized though, that becomes unnecessary, and we can switch to using SLF4J directly.
// The setup method is called after the game is initialized, so this is the perfect place to do this.
HotswapLoggerFinder.updateAllStrategies((name, module, level) -> new SLF4JPlatformLogger(LoggerFactory.getLogger(name)));
}
}
public static void bootstrap() {
if (!bootstrapped) {
bootstrapped = true;
// I use System.Logger for logging.
// LibJF ships a service loaded in System.LoggerFinder, which delegates to Fabric Loader and (later on) SLF4J (after setup).
if (LOGGER instanceof DelegateLogger) {
// If the ServiceLoader was able to find this service, any Logger generated will be a DelegateLogger since that class is used by the HotswapLoggerFinder.
// This means that redirection to SLF4J is already set up and we don't need to do anything.
LOGGER.debug("HotswapLoggerFinder was loaded successfully. No need to install JULBridge.");
} else {
// If Fabric Loader is run in production, the ServiceLoader may not pick up this implementation quickly enough.
// In that case, the JUL backend is selected and this cannot be changed, so we need to redirect logs from JUL instead.
// The JULBridge does exactly that by redirecting JUL logs to loggers created by its own instance of EarlyLoggerSetup.
JULBridge.install();
LOGGER.debug("JULBridge was installed. If available in your launcher, please enable the ServiceLoader fix for better performance.");
}
}
}
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.libjf.gson;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ClientOnly {
}

View File

@ -1,30 +0,0 @@
package io.gitlab.jfronny.libjf.gson;
import io.gitlab.jfronny.gson.*;
import net.fabricmc.loader.api.metadata.CustomValue;
import java.util.Map;
public class FabricLoaderGsonGenerator {
public static JsonElement toGson(CustomValue customValue) {
if (customValue == null) return null;
return switch (customValue.getType()) {
case OBJECT -> {
JsonObject jo = new JsonObject();
for (Map.Entry<String, CustomValue> value : customValue.getAsObject())
jo.add(value.getKey(), toGson(value.getValue()));
yield jo;
}
case ARRAY -> {
JsonArray jo = new JsonArray();
for (CustomValue value : customValue.getAsArray())
jo.add(toGson(value));
yield jo;
}
case STRING -> new JsonPrimitive(customValue.getAsString());
case NUMBER -> new JsonPrimitive(customValue.getAsNumber());
case BOOLEAN -> new JsonPrimitive(customValue.getAsBoolean());
case NULL -> JsonNull.INSTANCE;
};
}
}

View File

@ -1,7 +0,0 @@
package io.gitlab.jfronny.libjf.gson;
import io.gitlab.jfronny.gson.GsonBuilder;
public interface GsonAdapter {
void apply(GsonBuilder builder);
}

View File

@ -1,22 +0,0 @@
package io.gitlab.jfronny.libjf.gson;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.gson.ExclusionStrategy;
import io.gitlab.jfronny.gson.FieldAttributes;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
public class HiddenAnnotationExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT
? fieldAttributes.getAnnotation(ServerOnly.class) != null
: fieldAttributes.getAnnotation(ClientOnly.class) != null;
}
public static void register() {
GsonHolders.applyTransform(builder -> builder.setExclusionStrategies(new HiddenAnnotationExclusionStrategy()));
}
}

View File

@ -1,8 +0,0 @@
package io.gitlab.jfronny.libjf.gson;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ServerOnly {
}

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.libjf.log;
import io.gitlab.jfronny.commons.logger.HotswapLoggerFinder;
import io.gitlab.jfronny.commons.logger.LeveledLoggerFinder;
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
import org.jetbrains.annotations.Nullable;
public class EarlyLoggerSetup extends LeveledLoggerFinder {
static {
// When a logger is used before the game is initialized, SLF4J is not yet available.
// To support that, this implementation which redirects to Fabric Loader's internal logging abstraction is used instead.
// After the game is initialized, something calls LibJF.setup (usually preEntry), which replaces this factory with a SLF4J-based one.
HotswapLoggerFinder.updateAllStrategies((name, module, level) -> new LoaderPlatformLogger(name));
}
private final LeveledLoggerFinder delegate = new HotswapLoggerFinder();
@Override
public SystemLoggerPlus getLogger(String name, Module module, @Nullable System.Logger.Level level) {
return delegate.getLogger(name, module, level);
}
}

View File

@ -0,0 +1,87 @@
package io.gitlab.jfronny.libjf.log;
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
/**
* Based on SLF4JBridgeHandler, modified for LibJF.
*/
public class JULBridge extends Handler {
public static void install() {
var rootLogger = getRootLogger();
rootLogger.addHandler(new JULBridge());
boolean bridgeFound = false;
for (Handler handler : rootLogger.getHandlers()) {
if (handler instanceof JULBridge) {
bridgeFound = true;
continue;
}
rootLogger.removeHandler(handler);
}
if (!bridgeFound) {
// Someone is wrapping our handlers. No matter, just add it again.
rootLogger.addHandler(new JULBridge());
}
}
private static java.util.logging.Logger getRootLogger() {
return LogManager.getLogManager().getLogger("");
}
private JULBridge() {
}
@Override
public void flush() {
// Do nothing
}
@Override
public void close() {
// Do nothing
}
private final Module module = JULBridge.class.getClassLoader().getUnnamedModule();
private final EarlyLoggerSetup loggerFinder = new EarlyLoggerSetup();
private SystemLoggerPlus getLoggerFor(LogRecord record) {
return loggerFinder.getLogger(Objects.requireNonNullElse(record.getLoggerName(), ""), module);
}
private static System.Logger.Level getLevel(LogRecord record) {
System.Logger.Level level;
if (record.getLevel().equals(Level.OFF)) level = System.Logger.Level.OFF;
else if (record.getLevel().equals(Level.ALL)) level = System.Logger.Level.ALL;
else if (record.getLevel().intValue() >= Level.SEVERE.intValue()) {
level = System.Logger.Level.ERROR;
} else if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
level = System.Logger.Level.WARNING;
} else if (record.getLevel().intValue() >= Level.INFO.intValue()) {
level = System.Logger.Level.INFO;
} else {
level = System.Logger.Level.DEBUG;
}
return level;
}
@Override
public void publish(LogRecord record) {
if (record == null) return;
SystemLoggerPlus logger = getLoggerFor(record);
System.Logger.Level level = getLevel(record);
String message = record.getMessage();
ResourceBundle bundle = null;
if (message == null) message = "";
else bundle = record.getResourceBundle();
Object[] params = record.getParameters();
Throwable thrown = record.getThrown();
if (thrown == null) logger.log(level, bundle, message, params);
else logger.log(level, bundle, message, thrown, params);
}
}

View File

@ -0,0 +1,57 @@
package io.gitlab.jfronny.libjf.log;
import io.gitlab.jfronny.commons.logger.CompactLogger;
import net.fabricmc.loader.impl.util.log.Log;
import net.fabricmc.loader.impl.util.log.LogCategory;
import net.fabricmc.loader.impl.util.log.LogLevel;
public class LoaderPlatformLogger implements CompactLogger {
private final LogCategory category;
public LoaderPlatformLogger(String context, String... names) {
this.category = LogCategory.createCustom(context, names);
}
@Override
public String getName() {
return category.name;
}
@Override
public Level getLevel() {
for (Level value : Level.values()) {
if (value == Level.ALL || value == Level.OFF) continue;
if (isLoggable(value)) return value;
}
return Level.INFO; // should not happen, but if it does, INFO is a reasonable default
}
@Override
public void log(Level level, String message) {
Log.log(jplLevelToFabricLevel(level), category, message);
}
@Override
public void log(Level level, String message, Throwable throwable) {
Log.log(jplLevelToFabricLevel(level), category, message, throwable);
}
@Override
public boolean isLoggable(Level level) {
if (level == Level.ALL) return true;
if (level == Level.OFF) return false;
return Log.shouldLog(jplLevelToFabricLevel(level), category);
}
private LogLevel jplLevelToFabricLevel(Level jplLevel) {
return switch (jplLevel) {
case TRACE -> LogLevel.TRACE;
case DEBUG -> LogLevel.DEBUG;
case INFO -> LogLevel.INFO;
case WARNING -> LogLevel.WARN;
case ERROR -> LogLevel.ERROR;
// should not happen, but if it does, INFO is a reasonable default
default -> LogLevel.INFO;
};
}
}

View File

@ -0,0 +1,118 @@
package io.gitlab.jfronny.libjf.log;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.commons.logger.CompactLogger;
import io.gitlab.jfronny.commons.logger.impl.Formatter;
import org.slf4j.Logger;
import org.slf4j.spi.CallerBoundaryAware;
import org.slf4j.spi.LoggingEventBuilder;
import java.text.MessageFormat;
import java.util.Objects;
import java.util.ResourceBundle;
/**
* A {@link System.Logger} implementation that delegates to SLF4J.
* Adapted from slf4j-jdk-platform-logging for use in LibJF.
*
* @author Ceki Gülcü
* @author JFronny
*/
public class SLF4JPlatformLogger implements CompactLogger {
private static final String PRESUMED_CALLER_BOUNDARY = System.Logger.class.getName();
private final Logger slf4jLogger;
public SLF4JPlatformLogger(Logger logger) {
slf4jLogger = Objects.requireNonNull(logger);
}
@Override
public String getName() {
return slf4jLogger.getName();
}
@Override
public Level getLevel() {
if (slf4jLogger.isTraceEnabled()) return Level.TRACE;
else if (slf4jLogger.isDebugEnabled()) return Level.DEBUG;
else if (slf4jLogger.isInfoEnabled()) return Level.INFO;
else if (slf4jLogger.isWarnEnabled()) return Level.WARNING;
else if (slf4jLogger.isErrorEnabled()) return Level.ERROR;
else return Level.INFO; // should not happen, but if it does, INFO is a reasonable default
}
@Override
public boolean isLoggable(Level level) {
if (level == Level.ALL) return true;
if (level == Level.OFF) return false;
return slf4jLogger.isEnabledForLevel(jplLevelToSLF4JLevel(level));
}
private org.slf4j.event.Level jplLevelToSLF4JLevel(Level jplLevel) {
return switch (jplLevel) {
case TRACE -> org.slf4j.event.Level.TRACE;
case DEBUG -> org.slf4j.event.Level.DEBUG;
case INFO -> org.slf4j.event.Level.INFO;
case WARNING -> org.slf4j.event.Level.WARN;
case ERROR -> org.slf4j.event.Level.ERROR;
// should not happen, but if it does, INFO is a reasonable default
default -> org.slf4j.event.Level.INFO;
};
}
@Override
public void log(Level level, String message) {
log(level, null, message, null, null);
}
@Override
public void log(Level level, String message, Throwable throwable) {
log(level, null, message, throwable, null);
}
@Override
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
log(level, bundle, msg, thrown, null);
}
@Override
public void log(Level level, ResourceBundle bundle, String format, Object[] params) {
log(level, bundle, format, null, params);
}
@Override
public void log(Level jplLevel, ResourceBundle bundle, String msg, Throwable thrown, Object[] params) {
if (jplLevel == Level.OFF) return;
if (jplLevel == Level.ALL) {
performLog(org.slf4j.event.Level.TRACE, bundle, msg, thrown, params);
return;
}
org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevel);
if (slf4jLogger.isEnabledForLevel(slf4jLevel)) {
performLog(slf4jLevel, bundle, msg, thrown, params);
}
}
private void performLog(org.slf4j.event.Level slf4jLevel, ResourceBundle bundle, String msg, Throwable thrown, Object[] params) {
String message = Formatter.getResourceStringOrMessage(bundle, msg);
LoggingEventBuilder leb = slf4jLogger.makeLoggingEventBuilder(slf4jLevel);
if (thrown != null) {
leb = leb.setCause(thrown);
}
if (params != null && params.length > 0) {
// add the arguments to the logging event for possible processing by the backend
String[] strings = new String[params.length];
for (int i = 0; i < params.length; i++) {
leb = leb.addArgument(params[i]);
strings[i] = StringFormatter.toString(params[i]);
}
// The JDK uses a different formatting convention. We must invoke it now.
message = new MessageFormat(message).format(strings);
}
if (leb instanceof CallerBoundaryAware cba) {
cba.setCallerBoundary(PRESUMED_CALLER_BOUNDARY);
}
leb.log(message);
}
}

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.libjf.serialize;
import io.gitlab.jfronny.commons.serialize.emulated.DataElement;
import net.fabricmc.loader.api.metadata.CustomValue;
import java.util.Map;
public class FabricLoaderDataMapper {
public static DataElement toGson(CustomValue customValue) {
if (customValue == null) return null;
return switch (customValue.getType()) {
case OBJECT -> {
DataElement.Object jo = new DataElement.Object();
for (Map.Entry<String, CustomValue> value : customValue.getAsObject())
jo.members().put(value.getKey(), toGson(value.getValue()));
yield jo;
}
case ARRAY -> {
DataElement.Array jo = new DataElement.Array();
for (CustomValue value : customValue.getAsArray())
jo.elements().add(toGson(value));
yield jo;
}
case STRING -> new DataElement.Primitive.String(customValue.getAsString());
case NUMBER -> new DataElement.Primitive.Number(customValue.getAsNumber());
case BOOLEAN -> new DataElement.Primitive.Boolean(customValue.getAsBoolean());
case NULL -> new DataElement.Null();
};
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.libjf.serialize;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
public class LenientTransport extends JsonTransport {
@Override
public JsonWriter createWriter(Writer target) throws IOException {
return SerializationMode.asConfig(super.createWriter(target));
}
@Override
public JsonReader createReader(Reader source) {
return SerializationMode.asConfig(super.createReader(source));
}
}

View File

@ -0,0 +1,26 @@
package io.gitlab.jfronny.libjf.serialize;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
public class SerializationMode {
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> Reader asConfig(Reader reader) {
reader.setLenient(true)
.setSerializeSpecialFloatingPointValues(true);
return reader;
}
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> Writer asConfig(Writer writer) {
writer.setLenient(true)
.setSerializeSpecialFloatingPointValues(true)
.setSerializeNulls(true);
if (writer instanceof JsonWriter jw) {
jw.setIndent(" ")
.setNewline("\n")
.setOmitQuotes(true)
.setCommentUnexpectedNames(true);
}
return writer;
}
}

View File

@ -0,0 +1 @@
io.gitlab.jfronny.libjf.log.EarlyLoggerSetup

View File

@ -0,0 +1,37 @@
import io.gitlab.jfronny.scripts.deployDebug
import io.gitlab.jfronny.scripts.deployRelease
plugins {
`java-platform`
id("jf.maven-publish")
}
publishing {
publications {
register("mavenJava", MavenPublication::class) {
from(components["javaPlatform"])
}
}
}
tasks.publish { dependsOn(tasks.build) }
tasks.deployDebug.dependsOn(tasks.publish)
tasks.deployRelease.dependsOn(tasks.deployDebug)
tasks.withType(GenerateModuleMetadata::class) {
enabled = true
}
dependencies {
constraints {
for (proj in rootProject.allprojects) {
if (proj == project) {
continue
}
if (proj.name == "libjf-catalog") {
continue
}
api(project(proj.path))
}
}
}

View File

@ -0,0 +1,53 @@
import io.gitlab.jfronny.scripts.deployDebug
import io.gitlab.jfronny.scripts.deployRelease
plugins {
`version-catalog`
id("jf.maven-publish")
}
publishing {
publications {
register("mavenJava", MavenPublication::class) {
from(components["versionCatalog"])
}
}
}
tasks.publish { dependsOn(tasks.build) }
tasks.deployDebug.dependsOn(tasks.publish)
tasks.deployRelease.dependsOn(tasks.deployDebug)
tasks.withType(GenerateModuleMetadata::class) {
enabled = true
}
tasks.register("configureCatalog") {
doFirst {
doConfigureCatalog()
}
}
tasks.named("generateCatalogAsToml") {
dependsOn("configureCatalog")
}
fun doConfigureCatalog() {
for (proj in rootProject.allprojects) {
if (proj == project) {
continue
}
var catalogName = proj.name
catalogName = when (catalogName) {
"libjf-base" -> "base"
"libjf-bom" -> "bom"
"libjf" -> "libjf"
else -> catalogName.substring("libjf-".length)
}
catalog {
versionCatalog {
library(catalogName, "$group:${proj.name}:${proj.version}")
}
}
}
}

View File

@ -5,12 +5,11 @@ plugins {
}
base {
archivesName.set("libjf-config-commands")
archivesName = "libjf-config-commands"
}
dependencies {
val fabricVersion: String by rootProject.extra
api(devProject(":libjf-base"))
api(devProject(":libjf-config-core-v2"))
include(modImplementation(fabricApi.module("fabric-command-api-v2", fabricVersion))!!)
include(modImplementation("net.fabricmc.fabric-api:fabric-command-api-v2")!!)
}

View File

@ -7,9 +7,6 @@ plugins {
id("jf.codegen")
}
group = rootProject.group
version = rootProject.version
repositories {
mavenCentral()
maven("https://maven.frohnmeyer-wds.de/artifacts")
@ -17,16 +14,13 @@ repositories {
}
dependencies {
val commonsVersion: String by rootProject.extra
val gsonCompileVersion: String by rootProject.extra
val annotationsVersion: String by rootProject.extra
val javapoetVersion: String by rootProject.extra
implementation("io.gitlab.jfronny.gson:gson-compile-processor-core:$gsonCompileVersion")
implementation(libs.commons.serialize.generator.core)
implementation(devProject(":libjf-config-core-v2"))
implementation("org.jetbrains:annotations:$annotationsVersion")
implementation("io.gitlab.jfronny:commons:$commonsVersion")
implementation("io.gitlab.jfronny:commons-serialize-gson:$commonsVersion")
implementation("com.squareup:javapoet:$javapoetVersion")
implementation(libs.annotations)
implementation(libs.commons)
implementation(libs.commons.serialize.databind)
implementation(libs.commons.serialize.json)
implementation(libs.javapoet)
testAnnotationProcessor(sourceSets.main.get().output)
configurations.testAnnotationProcessor.get().extendsFrom(configurations.implementation.get())
}

View File

@ -3,7 +3,6 @@ package io.gitlab.jfronny.libjf.config.plugin;
import com.squareup.javapoet.ClassName;
public class Cl {
public static final ClassName MANIFOLD_EXTENSION = ClassName.get("manifold.ext.rt.api", "Extension");
public static final ClassName MANIFOLD_THIS = ClassName.get("manifold.ext.rt.api", "This");
}

View File

@ -15,8 +15,8 @@ public record ConfigClass(
) {
public static ConfigClass of(TypeElement element, String[] referencedConfigs, TypeName tweakerName, boolean hasManifold) {
ClassName className = ClassName.get(element);
String pkg = hasManifold ? "gsoncompile.extensions." + className.packageName() + "." + className.simpleNames().get(0) : className.packageName();
ClassName generatedClassName = ClassName.get(pkg, "JFC_" + className.simpleNames().get(0), className.simpleNames().subList(1, className.simpleNames().size()).toArray(String[]::new));
String pkg = hasManifold ? "gsoncompile.extensions." + className.packageName() + "." + className.simpleNames().getFirst() : className.packageName();
ClassName generatedClassName = ClassName.get(pkg, "JFC_" + className.simpleNames().getFirst(), className.simpleNames().subList(1, className.simpleNames().size()).toArray(String[]::new));
return new ConfigClass(element, ClassName.get(element), TypeName.get(element.asType()), generatedClassName, referencedConfigs, tweakerName);
}

View File

@ -2,8 +2,8 @@ package io.gitlab.jfronny.libjf.config.plugin;
import com.squareup.javapoet.*;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.gson.compile.processor.core.*;
import io.gitlab.jfronny.gson.reflect.TypeToken;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
import io.gitlab.jfronny.commons.serialize.generator.core.*;
import io.gitlab.jfronny.libjf.config.api.v2.*;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.DSL;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
@ -174,7 +174,7 @@ public class ConfigProcessor extends AbstractProcessor2 {
default -> {
code.add("$L, $L, ", e.min(), e.max());
if (tm instanceof DeclaredType dt && !dt.getTypeArguments().isEmpty()) {
code.add("$T.ofClass(new $T<$T>() {}.getType())", Type.class, TypeToken.class, tm);
code.add("$T.ofToken(new $T<$T>() {})", Type.class, TypeToken.class, tm);
} else code.add("$T.class", tm);
code.add(", $L, () -> $T.$L, value -> $T.$L = value", e.width(), source, name, source, name);
}

View File

@ -5,11 +5,10 @@ plugins {
}
base {
archivesName.set("libjf-config-core-v2")
archivesName = "libjf-config-core-v2"
}
dependencies {
val modmenuVersion: String by rootProject.extra
api(devProject(":libjf-base"))
modCompileOnly("com.terraformersmc:modmenu:$modmenuVersion")
modCompileOnly(libs.modmenu)
}

View File

@ -1,6 +1,5 @@
package io.gitlab.jfronny.libjf.config.api.v2;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.ConfigHolderImpl;
import java.nio.file.Path;
@ -15,7 +14,6 @@ public interface ConfigHolder {
* @return The config holder
*/
static ConfigHolder getInstance() {
LibJf.setup();
return ConfigHolderImpl.INSTANCE;
}

View File

@ -1,12 +1,20 @@
package io.gitlab.jfronny.libjf.config.api.v2;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.commons.serialize.MalformedDataException;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
import io.gitlab.jfronny.libjf.config.impl.dsl.NothingSerializedException;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import org.jetbrains.annotations.ApiStatus;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;
public interface EntryInfo<T> {
static EntryInfo<?> ofField(Field field) {
@ -68,13 +76,44 @@ public interface EntryInfo<T> {
* Set this entry's value to that of the element
* @param reader The reader to read from
*/
void loadFromJson(JsonReader reader) throws IOException, IllegalAccessException;
default <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void loadFromJson(Reader reader) throws TEx, IllegalAccessException {
try {
setValue(deserializeOneFrom(reader));
} catch (MalformedDataException e) {
LibJf.LOGGER.error("Could not read " + getName(), e);
} catch (NothingSerializedException ignored) {
}
}
/**
* Write the currently cached value to the writer
* @param writer The writer to write to
*/
void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException;
default <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void writeTo(Writer writer, String translationPrefix) throws TEx, IllegalAccessException {
try {
String commentText = JfConfigSafe.TRANSLATION_SUPPLIER.apply(translationPrefix + getName() + ".tooltip");
if (commentText != null) writer.comment(commentText);
if (getValueType().isEnum()) {
writer.comment("Valid: [" + Arrays.stream(getValueType().asEnum().options()).map(Objects::toString).collect(Collectors.joining(", ")) + "]");
}
writer.name(getName());
serializeOneTo(getValue(), translationPrefix, writer);
} catch (MalformedDataException e) {
LibJf.LOGGER.error("Could not write " + getName(), e);
}
}
default <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException {
return LibJf.MAPPER.getAdapter(getTypeToken()).deserialize(reader);
}
default <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serializeOneTo(T value, String translationPrefix, Writer writer) throws TEx, MalformedDataException {
LibJf.MAPPER.getAdapter(getTypeToken()).serialize(value, writer);
}
default TypeToken<T> getTypeToken() {
return (TypeToken<T>) Objects.requireNonNullElse(getValueType().asToken(), TypeToken.get(String.class));
}
/**
* @return Get the width for this entry

View File

@ -1,10 +1,16 @@
package io.gitlab.jfronny.libjf.config.api.v2.dsl;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import java.io.IOException;
import java.util.function.Consumer;
@FunctionalInterface
public interface Migration {
void apply(JsonReader reader) throws IOException;
static Migration of(ThrowingConsumer<SerializeReader<?, ?>, Exception> migration) {
Consumer<SerializeReader<?, ?>> safe = ((ThrowingConsumer<SerializeReader<?, ?>, RuntimeException>) (ThrowingConsumer) migration)::accept;
return safe::accept;
}
<TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void apply(Reader reader) throws TEx;
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.libjf.config.api.v2.type;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
import org.jetbrains.annotations.Nullable;
/**
@ -14,7 +15,19 @@ public sealed interface Type {
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);
else return new TUnknown(klazz, TypeToken.get(klazz));
}
static Type ofToken(TypeToken<?> token) {
java.lang.reflect.Type klazz = token.getRawType();
if (klazz == int.class || klazz == Integer.class) return TInt.INSTANCE;
else if (klazz == long.class || klazz == Long.class) return TLong.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, token);
}
default boolean isInt() {
@ -45,6 +58,10 @@ public sealed interface Type {
}
@Nullable java.lang.reflect.Type asClass();
@Nullable default TypeToken<?> asToken() {
java.lang.reflect.Type type = asClass();
return type == null ? null : TypeToken.get(type);
}
String getName();
@ -162,13 +179,13 @@ public sealed interface Type {
}
}
record TEnum<T>(@Nullable Class<T> klazz, String name, T[] options) implements Type {
record TEnum<T>(@Nullable Class<T> klazz, TypeToken<T> token, String name, T[] options) implements Type {
public TEnum(Class<T> klazz) {
this(klazz, klazz.getSimpleName(), klazz.getEnumConstants());
this(klazz, TypeToken.get(klazz), klazz.getSimpleName(), klazz.getEnumConstants());
}
public static TEnum<String> create(String name, String[] options) {
return new TEnum<>(null, name, options);
return new TEnum<>(null, null, name, options);
}
@Override
@ -181,6 +198,11 @@ public sealed interface Type {
return klazz;
}
@Override
public @Nullable TypeToken<?> asToken() {
return token;
}
@Override
public String getName() {
return name;
@ -194,12 +216,17 @@ public sealed interface Type {
}
}
record TUnknown(java.lang.reflect.Type klazz) implements Type {
record TUnknown(java.lang.reflect.Type klazz, TypeToken<?> token) implements Type {
@Override
public @Nullable java.lang.reflect.Type asClass() {
return klazz;
}
@Override
public @Nullable TypeToken<?> asToken() {
return token;
}
@Override
public String getName() {
return klazz instanceof Class<?> k ? k.getSimpleName() : klazz.getTypeName();

View File

@ -1,16 +1,17 @@
package io.gitlab.jfronny.libjf.config.impl;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.commons.serialize.emulated.DataElement;
import io.gitlab.jfronny.libjf.config.api.v2.Category;
import io.gitlab.jfronny.libjf.config.api.v2.JfConfig;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.CategoryBuilder;
import io.gitlab.jfronny.libjf.gson.FabricLoaderGsonGenerator;
import io.gitlab.jfronny.libjf.serialize.FabricLoaderDataMapper;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.metadata.CustomValue;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static io.gitlab.jfronny.libjf.config.impl.ConfigCore.MODULE_ID;
@ -43,11 +44,30 @@ public class AuxiliaryMetadata {
cv = cv.getAsObject().get("config");
}
}
if (cv != null) metaRef.meta = GsonHolders.API.getGson().fromJson(FabricLoaderGsonGenerator.toGson(cv), AuxiliaryMetadata.class);
if (cv != null) metaRef.meta = read(FabricLoaderDataMapper.toGson(cv));
});
return metaRef.meta;
}
public static AuxiliaryMetadata read(DataElement data) {
if (!(data instanceof DataElement.Object obj)) throw new IllegalArgumentException("AuxiliaryMetadata must be an object");
AuxiliaryMetadata meta = new AuxiliaryMetadata();
for (Map.Entry<String, DataElement> entry : obj.members().entrySet()) {
switch (entry.getKey()) {
case "referencedConfigs" -> {
if (!(entry.getValue() instanceof DataElement.Array arr)) throw new IllegalArgumentException("referencedConfigs must be an array");
meta.referencedConfigs = new LinkedList<>();
arr.elements().forEach(element -> {
if (!(element instanceof DataElement.Primitive.String str)) throw new IllegalArgumentException("referencedConfigs must be an array of strings");
meta.referencedConfigs.add(str.value());
});
}
default -> throw new IllegalArgumentException("Unknown key in AuxiliaryMetadata: " + entry.getKey());
}
}
return meta;
}
public List<String> referencedConfigs;
public void applyTo(CategoryBuilder<?> builder) {

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.throwable.Coerce;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.*;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.CategoryBuilder;
@ -21,7 +21,8 @@ public class CategoryBuilderImpl<Builder extends CategoryBuilderImpl<Builder>> i
public final Map<String, Consumer<ConfigCategory>> presets = new LinkedHashMap<>();
public final List<Supplier<List<ConfigInstance>>> referencedConfigs = new LinkedList<>();
public final List<Consumer<ConfigCategory>> verifiers = new LinkedList<>();
public final Map<String, Consumer<JsonReader>> migrations = new LinkedHashMap<>();
@SuppressWarnings("rawtypes") // recursive types aren't really needed here
public final Map<String, Consumer<SerializeReader>> migrations = new LinkedHashMap<>();
private boolean built = false;
public CategoryBuilderImpl(String id, String categoryPath) {
@ -174,7 +175,7 @@ public class CategoryBuilderImpl<Builder extends CategoryBuilderImpl<Builder>> i
public Builder addMigration(String element, Migration migration) {
checkBuilt();
if (migrations.containsKey(element)) LibJf.LOGGER.warn("Duplicate migration registered for " + categoryPath + id + ": " + element + ", overriding");
migrations.put(element, Coerce.consumer(migration::apply).addHandler(e -> LibJf.LOGGER.error("Could not apply migration for " + element, e)));
migrations.put(element, Coerce.<SerializeReader, Exception>consumer(migration::apply).addHandler(e -> LibJf.LOGGER.error("Could not apply migration for " + element, e)));
return asBuilder();
}

View File

@ -1,8 +1,9 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.gson.*;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.serialize.Token;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.*;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
@ -19,7 +20,7 @@ public class DefaultConfigIO {
// Actions cannot be cached since entries can change
if (Files.exists(path)) {
try (BufferedReader br = Files.newBufferedReader(path);
JsonReader jr = GsonHolders.CONFIG.getGson().newJsonReader(br)) {
JsonReader jr = LibJf.LENIENT_TRANSPORT.createReader(br)) {
runActions(id, createActions(c), jr);
} catch (Exception e) {
LibJf.LOGGER.error("Could not read config for " + id, e);
@ -29,31 +30,34 @@ public class DefaultConfigIO {
});
}
record Action(Consumer<JsonReader> task, boolean required) {
public Action(Consumer<JsonReader> task) {
record Action(Consumer<SerializeReader> task, boolean required) {
public Action(Consumer<SerializeReader> task) {
this(task, true);
}
}
private static void runActions(String id, Map<String, Action> actions, JsonReader reader) {
private static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void runActions(String id, Map<String, Action> actions, Reader reader) {
try {
if (reader.peek() != JsonToken.BEGIN_OBJECT) {
if (reader.peek() != Token.BEGIN_OBJECT) {
LibJf.LOGGER.error("Invalid config: Not a JSON object for " + id);
return;
}
Set<String> appeared = new HashSet<>();
reader.beginObject();
while (reader.peek() != JsonToken.END_OBJECT) {
while (reader.peek() != Token.END_OBJECT) {
String name = reader.nextName();
if (!actions.containsKey(name)) {
DefaultConfigIO.Action action = actions.get(name);
if (action == null) {
LibJf.LOGGER.warn("Unrecognized key in config for " + id + ": " + name);
reader.skipValue();
continue;
}
if (!appeared.add(name)) {
LibJf.LOGGER.warn("Duplicate key in config for " + id + ": " + name);
reader.skipValue();
continue;
}
actions.get(name).task.accept(reader);
action.task.accept(reader);
}
reader.endObject();
actions.forEach((name, action) -> {
@ -61,8 +65,8 @@ public class DefaultConfigIO {
LibJf.LOGGER.error("Missing entry in config for " + id + ": " + name);
}
});
} catch (IOException e) {
throw new JsonParseException("Could not read config", e);
} catch (Exception e) {
throw new IllegalStateException("Could not read config", e);
}
}
@ -71,7 +75,7 @@ public class DefaultConfigIO {
category.getEntries().forEach(entry -> actions.putIfAbsent(entry.getName(), new Action(reader -> {
try {
entry.loadFromJson(reader);
} catch (IllegalAccessException | IOException e) {
} catch (Exception e) {
LibJf.LOGGER.error("Could not set config entry value of " + entry.getName(), e);
}
})));
@ -91,7 +95,7 @@ public class DefaultConfigIO {
public static Consumer<ConfigInstance> writer(String id) {
return c -> c.getFilePath().ifPresent(path -> JfConfigWatchService.lock(path, () -> {
try (BufferedWriter bw = Files.newBufferedWriter(path);
JsonWriter jw = GsonHolders.CONFIG.getGson().newJsonWriter(bw)) {
JsonWriter jw = LibJf.LENIENT_TRANSPORT.createWriter(bw)) {
writeTo(jw, c);
} catch (Exception e) {
LibJf.LOGGER.error("Could not write config for " + id, e);

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.libjf.config.api.v2.*;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.CategoryBuilder;
import org.jetbrains.annotations.ApiStatus;
@ -17,7 +17,7 @@ public class DslConfigCategory implements ConfigCategory {
private final List<EntryInfo<?>> entries;
private final Map<String, Runnable> presets;
private final List<Supplier<List<ConfigInstance>>> referencedConfigs;
@ApiStatus.Internal public final Map<String, Consumer<JsonReader>> migrations;
@ApiStatus.Internal public final Map<String, Consumer<SerializeReader>> migrations;
private final Map<String, ConfigCategory> categories;
private final Supplier<ConfigInstance> root;
private final List<Consumer<ConfigCategory>> verifiers;
@ -31,7 +31,7 @@ public class DslConfigCategory implements ConfigCategory {
List<CategoryBuilder<?>> categories,
Supplier<ConfigInstance> root,
List<Consumer<ConfigCategory>> verifiers,
Map<String, Consumer<JsonReader>> migrations) {
Map<String, Consumer<SerializeReader>> migrations) {
this.id = id;
this.categoryPath = categoryPath;
this.translationPrefix = translationPrefix;

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.libjf.config.api.v2.*;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.CategoryBuilder;
import org.jetbrains.annotations.Nullable;
@ -23,7 +23,7 @@ public class DslConfigInstance extends DslConfigCategory implements ConfigInstan
List<CategoryBuilder<?>> categories,
Supplier<ConfigInstance> root,
List<Consumer<ConfigCategory>> verifiers,
Map<String, Consumer<JsonReader>> migrations,
Map<String, Consumer<SerializeReader>> migrations,
Consumer<ConfigInstance> load,
Consumer<ConfigInstance> write,
@Nullable Path path) {

View File

@ -1,16 +1,18 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.commons.serialize.MalformedDataException;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.commons.serialize.Token;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.Entry;
import io.gitlab.jfronny.libjf.config.api.v2.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Objects;
@ -60,12 +62,12 @@ public class DslEntryInfo<T> implements EntryInfo<T> {
} catch (IllegalAccessException ignored) {
}
//noinspection unchecked,rawtypes
return new DslEntryInfo<Object>(
return new DslEntryInfo<>(
field.getName(),
defaultValue,
() -> field.get(null),
v -> field.set(null, v),
(Type) Type.ofClass(field.getGenericType()),
Type.ofClass(field.getGenericType()),
entry == null ? 100 : entry.width(),
entry == null ? Double.NEGATIVE_INFINITY : entry.min(),
entry == null ? Double.POSITIVE_INFINITY : entry.max()
@ -119,7 +121,7 @@ public class DslEntryInfo<T> implements EntryInfo<T> {
}
if (valueOriginal != value) {
try {
setUnchecked(value);
setValue(cast(value));
} catch (IllegalAccessException e) {
LibJf.LOGGER.error("Could not write value", e);
}
@ -127,57 +129,48 @@ public class DslEntryInfo<T> implements EntryInfo<T> {
}
@Override
public void loadFromJson(JsonReader reader) throws IOException, IllegalAccessException {
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException {
var next = reader.peek();
if (type.isBool()) {
if (next == JsonToken.BOOLEAN) setUnchecked(reader.nextBoolean());
if (next == Token.BOOLEAN) return cast(reader.nextBoolean());
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected boolean but got " + next);
} else if (type.isString()) {
if (next == JsonToken.STRING || next == JsonToken.NUMBER) setUnchecked(reader.nextString());
else if (next == JsonToken.BOOLEAN) setUnchecked(Boolean.toString(reader.nextBoolean()));
else if (next == JsonToken.NULL) setUnchecked(null);
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected string but got " + next);
if (next == Token.STRING || next == Token.NUMBER) return cast(reader.nextString());
else if (next == Token.BOOLEAN) return cast(Boolean.toString(reader.nextBoolean()));
else if (next == Token.NULL) {
reader.nextNull();
return cast(null);
} else LibJf.LOGGER.error("Unexpected value for " + name + ": expected string but got " + next);
} else if (type.isInt()) {
if (next == JsonToken.NUMBER) setUnchecked(reader.nextInt());
if (next == Token.NUMBER) return cast(reader.nextInt());
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next);
} else if (type.isLong()) {
if (next == JsonToken.NUMBER) setUnchecked(reader.nextLong());
if (next == Token.NUMBER) return cast(reader.nextLong());
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next);
} else if (type.isDouble()) {
if (next == JsonToken.NUMBER) {
setUnchecked(reader.nextDouble());
if (next == Token.NUMBER) {
return cast(reader.nextDouble());
}
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next);
} else if (type.isFloat()) {
if (next == JsonToken.NUMBER) setUnchecked((float) reader.nextDouble());
if (next == Token.NUMBER) return cast((float) reader.nextDouble());
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next);
} else if (type.isEnum()) {
Type.TEnum<T> e = (Type.TEnum<T>) type;
if (next == JsonToken.STRING) setUnchecked(e.optionForString(reader.nextString()));
if (next == Token.STRING) return cast(e.optionForString(reader.nextString()));
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected string but got " + next);
} else {
setValue(GsonHolders.CONFIG.getGson().fromJson(reader, type.asClass()));
try {
return cast((T) LibJf.MAPPER.getAdapter(type.asToken()).deserialize(reader));
} catch (MalformedDataException e) {
LibJf.LOGGER.error("Could not read " + name, e);
}
}
throw new NothingSerializedException();
}
@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 commentText;
if ((commentText = JfConfigSafe.TRANSLATION_SUPPLIER.apply(translationPrefix + getName() + ".tooltip")) != null) {
writer.comment(commentText);
}
if (type.isEnum()) {
writer.comment("Valid: [" + Arrays.stream(((Type.TEnum<T>)type).options()).map(Objects::toString).collect(Collectors.joining(", ")) + "]");
}
writer.name(name);
GsonHolders.CONFIG.getGson().toJson(value, Objects.requireNonNullElse(type.asClass(), String.class), writer);
private T cast(Object object) {
return (T) object;
}
@Override

View File

@ -0,0 +1,4 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
public class NothingSerializedException extends RuntimeException {
}

View File

@ -26,7 +26,13 @@ public class JfConfigSafe implements PreLaunchEntrypoint {
}
}
TRANSLATION_SUPPLIER = s -> {
String translated = Language.getInstance().get(s);
String translated;
try {
translated = Language.getInstance().get(s);
} catch (Throwable t) {
LibJf.LOGGER.debug("Failed to translate key " + s, t);
return null;
}
return translated.equals(s) ? null : translated;
};
}

View File

@ -5,15 +5,13 @@ plugins {
}
base {
archivesName.set("libjf-config-network-v0")
archivesName = "libjf-config-network-v0"
}
dependencies {
val fabricVersion: String by rootProject.extra
val modmenuVersion: String by rootProject.extra
api(devProject(":libjf-base"))
api(devProject(":libjf-config-core-v2"))
modCompileOnly("com.terraformersmc:modmenu:$modmenuVersion")
include(modImplementation(fabricApi.module("fabric-networking-api-v1", fabricVersion))!!)
include(modImplementation(fabricApi.module("fabric-command-api-v2", fabricVersion))!!)
modCompileOnly(libs.modmenu)
include(modImplementation("net.fabricmc.fabric-api:fabric-networking-api-v1")!!)
include(modImplementation("net.fabricmc.fabric-api:fabric-command-api-v2")!!)
}

View File

@ -3,21 +3,24 @@ package io.gitlab.jfronny.libjf.config.impl.network.client;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ConfigurationCompletePacket;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ConfigurationPacket;
import io.gitlab.jfronny.libjf.config.impl.network.RequestRouter;
import io.gitlab.jfronny.libjf.config.impl.network.packet.RequestPacket;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ResponsePacket;
import net.fabricmc.fabric.api.client.networking.v1.*;
public class JfConfigNetworkClient {
public static boolean isAvailable = false;
public static void initialize() {
RequestRouter.initialize();
JfConfigNetworkCommands.initialize();
ClientPlayNetworking.registerGlobalReceiver(RequestRouter.RESPONSE_ID, (client, handler, buf, responseSender) -> {
RequestRouter.acceptResponse(buf, responseSender);
ClientPlayNetworking.registerGlobalReceiver(ResponsePacket.ID, (payload, context) -> {
RequestRouter.acceptResponse(payload, context.responseSender());
});
ClientPlayNetworking.registerGlobalReceiver(RequestRouter.REQUEST_ID, (client, handler, buf, responseSender) -> {
RequestRouter.acceptRequest(buf, responseSender);
ClientPlayNetworking.registerGlobalReceiver(RequestPacket.ID, (payload, context) -> {
RequestRouter.acceptRequest(payload, context.responseSender());
});
ClientConfigurationNetworking.registerGlobalReceiver(ConfigurationPacket.PACKET_TYPE, (packet, responseSender) -> {
isAvailable = packet.version() == RequestRouter.PROTOCOL_VERSION; // Handshake possible?
responseSender.sendPacket(new ConfigurationCompletePacket());
ClientConfigurationNetworking.registerGlobalReceiver(ConfigurationPacket.ID, (payload, context) -> {
isAvailable = payload.version() == RequestRouter.PROTOCOL_VERSION; // Handshake possible?
context.responseSender().sendPacket(new ConfigurationCompletePacket());
});
ClientConfigurationConnectionEvents.INIT.register((handler, client) -> {
isAvailable = false; // Reset for new server connection

View File

@ -1,84 +1,93 @@
package io.gitlab.jfronny.libjf.config.impl.network;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ConfigurationCompletePacket;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ConfigurationPacket;
import io.gitlab.jfronny.libjf.config.impl.network.packet.RequestPacket;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ResponsePacket;
import io.netty.buffer.ByteBuf;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import java.util.*;
public class RequestRouter {
public static final String MOD_ID = "libjf-config-network-v0";
public static final Identifier RESPONSE_ID = new Identifier(MOD_ID, "response");
public static final Identifier REQUEST_ID = new Identifier(MOD_ID, "request");
public static int PROTOCOL_VERSION = 1;
private static final Map<String, RequestHandler> persistendHandlers = new HashMap<>();
private static final Map<Long, Request> currentRequests = new HashMap<>(); //TODO implement timeout and prune old requests
private static final Random random = new Random();
public static void acceptResponse(PacketByteBuf buf, PacketSender responseSender) {
Request request = currentRequests.remove(buf.readLong());
public static void initialize() {
PayloadTypeRegistry.playC2S().register(RequestPacket.ID, RequestPacket.CODEC);
PayloadTypeRegistry.playS2C().register(RequestPacket.ID, RequestPacket.CODEC);
PayloadTypeRegistry.playC2S().register(ResponsePacket.ID, ResponsePacket.CODEC);
PayloadTypeRegistry.playS2C().register(ResponsePacket.ID, ResponsePacket.CODEC);
PayloadTypeRegistry.configurationS2C().register(ConfigurationPacket.ID, ConfigurationPacket.CODEC);
PayloadTypeRegistry.configurationC2S().register(ConfigurationCompletePacket.ID, ConfigurationCompletePacket.CODEC);
}
public static void acceptResponse(ResponsePacket response, PacketSender responseSender) {
Request request = currentRequests.remove(response.request());
if (request != null) {
switch (buf.readInt()) {
case 0 -> request.responseHandler.onSuccess(buf);
switch (response.status()) {
case 0 -> request.responseHandler.onSuccess(response.aux());
case 1 -> request.responseHandler.onNotFound();
case 2 -> request.responseHandler.onDeny();
case 3 -> request.responseHandler.onFailure(buf.readString());
case 3 -> request.responseHandler.onFailure(response.aux().readString());
default -> request.responseHandler.onFailure("Unrecognized error received");
}
}
}
public static void acceptRequest(PacketByteBuf buf, PacketSender responseSender) {
PacketByteBuf resp = PacketByteBufs.create();
long id = buf.readLong();
resp.writeLong(id);
public static void acceptRequest(RequestPacket request, PacketSender responseSender) {
long id = request.request();
int mode = 3;
PacketByteBuf aux = PacketByteBufs.create();
try {
if (buf.readBoolean()) {
if (request.parent() == null) {
// persistent
handleRequest(resp, id, buf, persistendHandlers.get(buf.readString()));
mode = handleRequest(aux, id, request.aux(), persistendHandlers.get(request.name()));
} else {
// followup
Request parent = currentRequests.get(buf.readLong());
Request parent = currentRequests.get(request.parent());
if (parent == null) {
resp.writeInt(1);
mode = 1;
} else {
String key = buf.readString();
String key = request.name();
RequestHandler handler = parent.temporaryHandlers.get(key);
if (handler == null) handler = persistendHandlers.get(key);
handleRequest(resp, id, buf, handler);
mode = handleRequest(aux, id, request.aux(), handler);
}
}
} catch (Throwable t) {
LibJf.LOGGER.error("Cannot complete request", t);
resp.writeInt(3);
resp.writeString(t.getMessage() == null ? "null" : t.getMessage());
aux.clear();
aux.writeString(t.getMessage() == null ? "null" : t.getMessage());
} finally {
responseSender.sendPacket(RESPONSE_ID, resp);
responseSender.sendPacket(new ResponsePacket(id, mode, aux));
}
}
private static void handleRequest(PacketByteBuf resp, long id, PacketByteBuf buf, RequestHandler handler) throws Throwable {
private static int handleRequest(ByteBuf aux, long id, PacketByteBuf buf, RequestHandler handler) throws Throwable {
if (handler == null) {
resp.writeInt(1);
return 1;
} else {
PacketByteBuf response = handler.handle(
buf,
(responseSender, name, body, responseHandler, temporaryHandlers) ->
RequestRouter.sendRequest(responseSender, id, name, body, responseHandler, temporaryHandlers)
);
resp.writeInt(0);
if (response != null) resp.writeBytes(response.copy());
if (response != null) aux.writeBytes(response.copy());
return 0;
}
}
public static void deny(PacketByteBuf buf, PacketSender responseSender) {
PacketByteBuf resp = PacketByteBufs.create();
resp.writeLong(buf.readLong());
resp.writeInt(2);
responseSender.sendPacket(RESPONSE_ID, resp);
public static void deny(long id, PacketSender responseSender) {
responseSender.sendPacket(new ResponsePacket(id, 2, PacketByteBufs.empty()));
}
public static void registerHandler(String name, RequestHandler handler) {
@ -98,15 +107,7 @@ public class RequestRouter {
} while (keys.contains(id));
currentRequests.put(id, new Request(temporaryHandlers, responseHandler));
}
PacketByteBuf req = PacketByteBufs.create();
req.writeLong(id);
req.writeBoolean(parent == null);
if (parent != null) req.writeLong(parent);
req.writeString(name);
if (body != null) {
req.writeBytes(body.copy());
}
responseSender.sendPacket(REQUEST_ID, req);
responseSender.sendPacket(new RequestPacket(id, parent, name, PacketByteBufs.copy(body)));
}
private record Request(Map<String, RequestHandler> temporaryHandlers, ResponseHandler responseHandler) {}

View File

@ -1,25 +1,17 @@
package io.gitlab.jfronny.libjf.config.impl.network.packet;
import io.gitlab.jfronny.libjf.config.impl.network.RequestRouter;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.minecraft.network.PacketByteBuf;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
public record ConfigurationCompletePacket() implements FabricPacket {
public static final Identifier ID = new Identifier(RequestRouter.MOD_ID, "handshake_complete");
public static final PacketType<ConfigurationCompletePacket> PACKET_TYPE = PacketType.create(ID, ConfigurationCompletePacket::new);
public ConfigurationCompletePacket(PacketByteBuf buf) {
this();
}
public record ConfigurationCompletePacket() implements CustomPayload {
public static final CustomPayload.Id<ConfigurationCompletePacket> ID = new CustomPayload.Id<>(new Identifier(RequestRouter.MOD_ID, "handshake_complete"));
public static final PacketCodec<ByteBuf, ConfigurationCompletePacket> CODEC = PacketCodec.unit(new ConfigurationCompletePacket());
@Override
public void write(PacketByteBuf buf) {
}
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View File

@ -1,26 +1,18 @@
package io.gitlab.jfronny.libjf.config.impl.network.packet;
import io.gitlab.jfronny.libjf.config.impl.network.RequestRouter;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.minecraft.network.PacketByteBuf;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
public record ConfigurationPacket(int version) implements FabricPacket {
public static final Identifier ID = new Identifier(RequestRouter.MOD_ID, "handshake");
public static final PacketType<ConfigurationPacket> PACKET_TYPE = PacketType.create(ID, ConfigurationPacket::new);
public ConfigurationPacket(PacketByteBuf buf) {
this(buf.readInt());
}
public record ConfigurationPacket(int version) implements CustomPayload {
public static final CustomPayload.Id<ConfigurationPacket> ID = new CustomPayload.Id<>(new Identifier(RequestRouter.MOD_ID, "handshake"));
public static final PacketCodec<ByteBuf, ConfigurationPacket> CODEC = PacketCodecs.INTEGER.xmap(ConfigurationPacket::new, ConfigurationPacket::version);
@Override
public void write(PacketByteBuf buf) {
buf.writeInt(version);
}
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View File

@ -0,0 +1,36 @@
package io.gitlab.jfronny.libjf.config.impl.network.packet;
import io.gitlab.jfronny.libjf.config.impl.network.RequestRouter;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.function.Function;
public record RequestPacket(long request, @Nullable Long parent, String name, PacketByteBuf aux) implements CustomPayload {
public static final Id<RequestPacket> ID = new CustomPayload.Id<>(new Identifier(RequestRouter.MOD_ID, "request"));
public static final PacketCodec<PacketByteBuf, RequestPacket> CODEC = PacketCodec.tuple(
PacketCodecs.VAR_LONG, RequestPacket::request,
PacketCodecs.optional(PacketCodecs.VAR_LONG), RequestPacket::optionalParent,
PacketCodecs.STRING, RequestPacket::name,
PacketCodec.of(PacketByteBuf::writeBytes, PacketByteBufs::copy), RequestPacket::aux,
RequestPacket::new);
private RequestPacket(long request, Optional<Long> parent, String name, PacketByteBuf aux) {
this(request, parent.orElse(null), name, aux);
}
private Optional<Long> optionalParent() {
return Optional.ofNullable(parent);
}
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View File

@ -0,0 +1,24 @@
package io.gitlab.jfronny.libjf.config.impl.network.packet;
import io.gitlab.jfronny.libjf.config.impl.network.RequestRouter;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
public record ResponsePacket(long request, int status, PacketByteBuf aux) implements CustomPayload {
public static final CustomPayload.Id<ResponsePacket> ID = new CustomPayload.Id<>(new Identifier(RequestRouter.MOD_ID, "response"));
public static final PacketCodec<PacketByteBuf, ResponsePacket> CODEC = PacketCodec.tuple(
PacketCodecs.VAR_LONG, ResponsePacket::request,
PacketCodecs.INTEGER, ResponsePacket::status,
PacketCodec.of(PacketByteBuf::writeBytes, PacketByteBufs::copy), ResponsePacket::aux,
ResponsePacket::new
);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View File

@ -1,18 +1,15 @@
package io.gitlab.jfronny.libjf.config.impl.network.rci.entry;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.commons.serialize.MalformedDataException;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.libjf.config.api.v2.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.impl.network.rci.MirrorConfigCategory;
import io.gitlab.jfronny.libjf.config.impl.network.rci.MirrorObject;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.minecraft.network.PacketByteBuf;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public abstract class MirrorEntryInfoBase<T> extends MirrorObject implements EntryInfo<T> {
protected final MirrorConfigCategory category;
protected final String entryName;
@ -34,13 +31,23 @@ public abstract class MirrorEntryInfoBase<T> extends MirrorObject implements Ent
}
@Override
public void loadFromJson(JsonReader reader) throws IOException, IllegalAccessException {
throw new UnsupportedEncodingException();
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void loadFromJson(Reader reader) {
throw new UnsupportedOperationException();
}
@Override
public void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException {
throw new UnsupportedEncodingException();
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void writeTo(Writer writer, String translationPrefix) {
throw new UnsupportedOperationException();
}
@Override
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> T deserializeOneFrom(Reader reader) throws TEx, MalformedDataException {
throw new UnsupportedOperationException();
}
@Override
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serializeOneTo(T value, String translationPrefix, Writer writer) throws TEx, MalformedDataException {
throw new UnsupportedOperationException();
}
@Override
@ -51,7 +58,7 @@ public abstract class MirrorEntryInfoBase<T> extends MirrorObject implements Ent
}
@Override
public void reset() throws IllegalAccessException {
public void reset() {
PacketByteBuf buf = PacketByteBufs.create();
writePath(buf);
sendRequest("resetEntry", buf);

View File

@ -5,6 +5,8 @@ import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ConfigurationCompletePacket;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ConfigurationPacket;
import io.gitlab.jfronny.libjf.config.impl.network.RequestRouter;
import io.gitlab.jfronny.libjf.config.impl.network.packet.RequestPacket;
import io.gitlab.jfronny.libjf.config.impl.network.packet.ResponsePacket;
import io.gitlab.jfronny.libjf.config.impl.network.rci.entry.Datatype;
import io.gitlab.jfronny.libjf.config.impl.network.rci.entry.MirrorEntryInfo;
import net.fabricmc.fabric.api.networking.v1.*;
@ -18,19 +20,20 @@ import java.util.function.Consumer;
public class JfConfigNetworkServer {
public static void initialize() {
ServerPlayNetworking.registerGlobalReceiver(RequestRouter.REQUEST_ID, (server, player, handler, buf, responseSender) -> {
if (authenticate(player)) RequestRouter.acceptRequest(buf, responseSender);
else RequestRouter.deny(buf, responseSender);
RequestRouter.initialize();
ServerPlayNetworking.registerGlobalReceiver(RequestPacket.ID, (payload, context) -> {
if (authenticate(context.player())) RequestRouter.acceptRequest(payload, context.responseSender());
else RequestRouter.deny(payload.request(), context.responseSender());
});
ServerPlayNetworking.registerGlobalReceiver(RequestRouter.RESPONSE_ID, (server, player, handler, buf, responseSender) -> {
if (authenticate(player)) RequestRouter.acceptResponse(buf, responseSender);
else RequestRouter.deny(buf, responseSender);
ServerPlayNetworking.registerGlobalReceiver(ResponsePacket.ID, (payload, context) -> {
if (authenticate(context.player())) RequestRouter.acceptResponse(payload, context.responseSender());
else RequestRouter.deny(payload.request(), context.responseSender());
});
ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.PACKET_TYPE, (packet, networkHandler, responseSender) -> {
networkHandler.completeTask(JfConfigNetworkConfigurationTask.KEY);
ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.ID, (payload, context) -> {
context.networkHandler().completeTask(JfConfigNetworkConfigurationTask.KEY);
});
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) {
if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.ID)) {
handler.addTask(new JfConfigNetworkConfigurationTask());
}
});

View File

@ -5,12 +5,11 @@ plugins {
}
base {
archivesName.set("libjf-config-ui-tiny")
archivesName = "libjf-config-ui-tiny"
}
dependencies {
val fabricVersion: String by rootProject.extra
api(devProject(":libjf-base"))
api(devProject(":libjf-config-core-v2"))
include(fabricApi.module("fabric-resource-loader-v0", fabricVersion))
include("net.fabricmc.fabric-api:fabric-resource-loader-v0")
}

View File

@ -1,11 +1,8 @@
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import io.gitlab.jfronny.commons.ref.R;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.minecraft.SharedConstants;
import net.minecraft.client.font.TextHandler;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
@ -18,6 +15,7 @@ import net.minecraft.client.util.math.Rect2i;
import net.minecraft.screen.ScreenTexts;
import net.minecraft.text.*;
import net.minecraft.util.Formatting;
import net.minecraft.util.StringHelper;
import net.minecraft.util.Util;
import net.minecraft.util.math.MathHelper;
import org.apache.commons.lang3.StringUtils;
@ -161,7 +159,7 @@ public class EditorScreen extends ScreenWithSaveHook {
if (super.charTyped(chr, modifiers)) {
return true;
}
if (SharedConstants.isValidChar(chr)) {
if (StringHelper.isValidChar(chr)) {
this.currentPageSelectionManager.insert(Character.toString(chr));
this.invalidatePageContent();
return true;
@ -296,12 +294,6 @@ public class EditorScreen extends ScreenWithSaveHook {
final int maxScroll = this.getMaxScroll();
final boolean showScrollbar = maxScroll > 0;
if (client.world == null) {
RenderSystem.setShaderColor(0.125f, 0.125f, 0.125f, 1.0f);
context.drawTexture(Screen.OPTIONS_BACKGROUND_TEXTURE, 0, HEADER_SIZE, showScrollbar ? width - SCROLLBAR_SIZE : width, height - FOOTER_SIZE + (int)scrollAmount, width - SCROLLBAR_SIZE, height - HEADER_SIZE - FOOTER_SIZE, 32, 32);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
}
context.enableScissor(0, HEADER_SIZE, width - SCROLLBAR_SIZE, height - FOOTER_SIZE);
PageContent pageContent = this.getPageContent();
for (Line line : pageContent.lines) {

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.commons.serialize.Transport;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.v2.EntryInfo;
@ -13,6 +14,8 @@ import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
import java.io.IOException;
// IDEA doesn't like this, but it does work in practice
public class TinyConfigScreenFactory implements ConfigScreenFactory<Screen, TinyConfigScreenFactory.Built> {
@Override
@ -21,41 +24,48 @@ public class TinyConfigScreenFactory implements ConfigScreenFactory<Screen, Tiny
&& config.getPresets().keySet().stream().allMatch(s -> s.equals(CategoryBuilder.CONFIG_PRESET_DEFAULT))
&& config.getReferencedConfigs().isEmpty()
&& config.getCategories().isEmpty()) {
EntryInfo entry = config.getEntries().get(0);
EntryInfo<?> entry = config.getEntries().getFirst();
Type type = entry.supportsRepresentation() ? entry.getValueType() : null;
if (type != null && !type.isInt() && !type.isLong() && !type.isFloat() && !type.isDouble() && !type.isString() && !type.isBool() && !type.isEnum()) {
final String jsonified;
try {
jsonified = GsonHolders.CONFIG.getGson().toJson(entry.getValue());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
String key = config.getTranslationPrefix() + entry.getName();
return new Built(new EditorScreen(
Text.translatable(key),
I18n.hasTranslation(key + ".tooltip") ? Text.translatable(key + ".tooltip") : null,
parent,
jsonified,
json -> {
try {
entry.setValue(GsonHolders.CONFIG.getGson().fromJson(json, type.asClass()));
config.write();
} catch (Throwable e) {
LibJf.LOGGER.error("Could not write element", e);
SystemToast.add(
MinecraftClient.getInstance().getToastManager(),
SystemToast.Type.PACK_LOAD_FAILURE,
Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.title"),
Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.description")
);
}
}
));
return createJson(config, parent, entry);
}
}
return new Built(new TinyConfigScreen(config, parent));
}
private <T> Built createJson(ConfigInstance config, Screen parent, EntryInfo<T> entry) {
final String jsonified;
try {
var value = entry.getValue();
jsonified = LibJf.LENIENT_TRANSPORT.write(writer -> entry.serializeOneTo(value, config.getTranslationPrefix(), writer));
} catch (IllegalAccessException | IOException e) {
throw new RuntimeException(e);
}
String key = config.getTranslationPrefix() + entry.getName();
return new Built(new EditorScreen(
Text.translatable(key),
I18n.hasTranslation(key + ".tooltip") ? Text.translatable(key + ".tooltip") : null,
parent,
jsonified,
json -> {
try {
entry.setValue(LibJf.LENIENT_TRANSPORT.read(
json,
(Transport.Returnable<JsonReader, ? extends T, IOException>) entry::deserializeOneFrom));
config.write();
} catch (Throwable e) {
LibJf.LOGGER.error("Could not write element", e);
SystemToast.add(
MinecraftClient.getInstance().getToastManager(),
SystemToast.Type.PACK_LOAD_FAILURE,
Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.title"),
Text.translatable("libjf-config-ui-tiny.entry.json.write.fail.description")
);
}
}
));
}
@Override
public int getPriority() {
return 0;

View File

@ -1,7 +1,8 @@
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry;
import io.gitlab.jfronny.commons.ref.R;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.commons.serialize.Transport;
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
import io.gitlab.jfronny.commons.throwable.Try;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.ConfigCategory;
@ -19,6 +20,7 @@ import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Language;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
@ -171,7 +173,7 @@ public class EntryInfoWidgetBuilder {
final String jsonified;
if (state.tempValue == null) {
try {
jsonified = GsonHolders.CONFIG.getGson().toJson(state.cachedValue);
jsonified = LibJf.LENIENT_TRANSPORT.write(writer -> info.serializeOneTo(state.cachedValue, config.getTranslationPrefix(), writer));
} catch (Throwable e) {
LibJf.LOGGER.error("Could not stringify element", e);
SystemToast.add(
@ -193,7 +195,10 @@ public class EntryInfoWidgetBuilder {
jsonified,
json -> {
try {
state.updateCache(GsonHolders.CONFIG.getGson().fromJson(json, info.getValueType().asClass()));
state.updateCache(LibJf.LENIENT_TRANSPORT.read(
json,
(Transport.Returnable<JsonReader, ? extends T, IOException>) info::deserializeOneFrom
));
state.tempValue = null;
} catch (Throwable e) {
LibJf.LOGGER.error("Could not write element", e);

View File

@ -19,18 +19,15 @@ import java.util.function.Supplier;
@Environment(EnvType.CLIENT)
public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEntry> {
private final TextRenderer textRenderer;
private final boolean background;
public EntryListWidget(MinecraftClient client, TextRenderer tr, int width, int height, int top) {
super(client, width, height, top, 25);
this.centerListVertically = false;
textRenderer = tr;
background = client.world == null;
setRenderBackground(background);
}
@Override
public int getScrollbarPositionX() {
public int getScrollbarX() {
return this.width -7;
}
@ -82,12 +79,6 @@ public class EntryListWidget extends ElementListWidget<EntryListWidget.ConfigEnt
@Override
protected void renderDecorations(DrawContext context, int mouseX, int mouseY) {
super.renderDecorations(context, mouseX, mouseY);
if (background) return;
// Background texture at the top of the screen
context.setShaderColor(0.25f, 0.25f, 0.25f, 1.0f);
context.drawTexture(Screen.OPTIONS_BACKGROUND_TEXTURE, getX(), 0, 0.0f, 0.0f, width, getY(), 32, 32);
context.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
// Gradient at the top of the screen
context.fillGradient(RenderLayer.getGuiOverlay(), getX(), getY(), getRight(), getY() + 4, 0xff000000, 0x00000000, 0);

View File

@ -11,7 +11,6 @@ import java.util.List;
public class PresetListWidget extends ElementListWidget<PresetListWidget.PresetEntry> {
public PresetListWidget(MinecraftClient client, int width, int height, int top, int itemHeight) {
super(client, width, height, top, itemHeight);
setRenderBackground(client.world == null);
}
public void addButton(ClickableWidget button) {
@ -19,8 +18,8 @@ public class PresetListWidget extends ElementListWidget<PresetListWidget.PresetE
}
@Override
public int getScrollbarPositionX() {
return this.width -7;
protected int getScrollbarX() {
return this.width - 7;
}
@Override

View File

@ -2,6 +2,7 @@ package io.gitlab.jfronny.libjf.config.impl.ui.tiny.presets;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.v2.ConfigCategory;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.CategoryBuilder;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@ -31,7 +32,7 @@ public class PresetsScreen extends Screen {
super.init();
PresetListWidget list = new PresetListWidget(this.client, this.width, this.height, 32, 25);
for (Map.Entry<String, Runnable> entry : config.getPresets().entrySet()) {
list.addButton(ButtonWidget.builder(Text.translatable(config.getTranslationPrefix() + entry.getKey()),
list.addButton(ButtonWidget.builder(CategoryBuilder.CONFIG_PRESET_DEFAULT.equals(entry.getKey()) ? Text.translatable(entry.getKey()) : Text.translatable(config.getTranslationPrefix() + entry.getKey()),
button -> {
LibJf.LOGGER.info("Preset selected: " + entry.getKey());
entry.getValue().run();

View File

@ -1,4 +1,6 @@
{
"libjf-config-ui-tiny.entry.json.read.fail.title": "Could not read",
"libjf-config-ui-tiny.entry.json.read.fail.description": "The given entry could not be stringified. Please edit the config manually"
"libjf-config-ui-tiny.entry.json.read.fail.description": "The given entry could not be read from a string. Please edit the config manually",
"libjf-config-ui-tiny.entry.json.write.fail.title": "Could not write",
"libjf-config-ui-tiny.entry.json.write.fail.description": "The given entry could not be stringified. Please edit the config manually"
}

View File

@ -1,7 +1,10 @@
package io.gitlab.jfronny.libjf.config.test.tiny;
import io.gitlab.jfronny.commons.data.String2ObjectMap;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
import io.gitlab.jfronny.libjf.config.api.v2.JfCustomConfig;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.DSL;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
public class TestConfig implements JfCustomConfig {
private int value1 = 0;
@ -12,6 +15,7 @@ public class TestConfig implements JfCustomConfig {
private int value4 = 0;
private String value5 = "";
private boolean value6 = false;
private String2ObjectMap<String> map = new String2ObjectMap<>();
@Override
public void register(DSL.Defaulted dsl) {
@ -30,6 +34,8 @@ public class TestConfig implements JfCustomConfig {
.value("value5", value5, () -> value5, v -> value5 = v)
).category("ca6", builder1 -> builder1
.value("value6", value6, () -> value6, v -> value6 = v)
).category("ca7", builder1 -> builder1
.value("mappy", map, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Type.ofToken(new TypeToken<String2ObjectMap<String>>() {}), 100, () -> map, v -> map = v)
)
);
}

View File

@ -5,6 +5,7 @@ import io.gitlab.jfronny.libjf.config.api.v2.ui.ConfigScreenFactory;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.option.OptionsScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.DirectionalLayoutWidget;
import net.minecraft.client.gui.widget.GridWidget;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.*;
@ -25,8 +26,8 @@ public abstract class OptionsScreenMixin extends Screen {
super(title);
}
@Inject(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/GridWidget;refreshPositions()V"), locals = LocalCapture.CAPTURE_FAILHARD)
void injectButton(CallbackInfo ci, GridWidget gridWidget, GridWidget.Adder adder) {
@Inject(method = "init()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ThreePartsLayoutWidget;addBody(Lnet/minecraft/client/gui/widget/Widget;)Lnet/minecraft/client/gui/widget/Widget;"), locals = LocalCapture.CAPTURE_FAILHARD)
void injectButton(CallbackInfo ci, DirectionalLayoutWidget directionalLayoutWidget, DirectionalLayoutWidget directionalLayoutWidget2, GridWidget gridWidget, GridWidget.Adder adder) {
ConfigHolder.getInstance().getRegistered().forEach((key, config) -> {
adder.add(
createButton(Text.translatable(key + ".jfconfig.title"), () -> ConfigScreenFactory.getInstance()

View File

@ -5,12 +5,11 @@ plugins {
}
base {
archivesName.set("libjf-data-manipulation-v0")
archivesName = "libjf-data-manipulation-v0"
}
dependencies {
val fabricVersion: String by rootProject.extra
api(devProject(":libjf-base"))
api(devProject(":libjf-unsafe-v0"))
modApi(fabricApi.module("fabric-api-base", fabricVersion))
modApi("net.fabricmc.fabric-api:fabric-api-base")
}

View File

@ -1,6 +1,8 @@
package io.gitlab.jfronny.libjf.data.manipulation.api;
import io.gitlab.jfronny.commons.LazySupplier;
import io.gitlab.jfronny.commons.concurrent.ScopedValue;
import io.gitlab.jfronny.commons.throwable.ExceptionWrapper;
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import io.gitlab.jfronny.libjf.LibJf;
@ -19,20 +21,17 @@ import java.util.function.Supplier;
public class UserResourceEvents {
public static <TVal, TEx extends Throwable> TVal disable(ThrowingSupplier<TVal, TEx> then) throws TEx {
try {
ResourcePackHook.setDisabled(true);
return then.get();
}
finally {
ResourcePackHook.setDisabled(false);
return ScopedValue.getWhere(ResourcePackHook.DISABLED, true, then.orThrow());
} catch (ExceptionWrapper ew) {
throw (TEx) ExceptionWrapper.unwrap(ew);
}
}
public static <TEx extends Throwable> void disable(ThrowingRunnable<TEx> then) throws TEx {
try {
ResourcePackHook.setDisabled(true);
then.run();
} finally {
ResourcePackHook.setDisabled(false);
ScopedValue.runWhere(ResourcePackHook.DISABLED, true, then.orThrow());
} catch (ExceptionWrapper ew) {
throw (TEx) ExceptionWrapper.unwrap(ew);
}
}

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.libjf.data.manipulation.impl;
import io.gitlab.jfronny.commons.LazySupplier;
import io.gitlab.jfronny.commons.concurrent.ScopedValue;
import io.gitlab.jfronny.libjf.data.manipulation.api.UserResourceEvents;
import net.minecraft.resource.*;
import net.minecraft.resource.metadata.ResourceMetadataReader;
@ -9,22 +10,16 @@ import org.jetbrains.annotations.ApiStatus;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused")
@ApiStatus.Internal
public class ResourcePackHook {
private static final Map<Long, Boolean> disabled = new HashMap<>();
@ApiStatus.Internal
public static void setDisabled(boolean disabled) {
ResourcePackHook.disabled.put(Thread.currentThread().getId(), disabled);
}
public static final ScopedValue<Boolean> DISABLED = new ScopedValue<>();
@ApiStatus.Internal
public static boolean isDisabled() {
return disabled.getOrDefault(Thread.currentThread().getId(), false);
private static boolean isDisabled() {
Boolean b = DISABLED.orElse(false);
return b != null && b; // this null check should not be needed, but it's here just in case
}
public static InputSupplier<InputStream> hookOpenRoot(InputSupplier<InputStream> value, ResourcePack pack, String[] fileName) {

View File

@ -11,7 +11,7 @@ public class TestEntrypoint implements ModInitializer {
// This should prevent resource packs from doing anything if my hooks are working and
UserResourceEvents.OPEN.register((type, id, previous, pack) -> {
if (pack instanceof DirectoryResourcePack) {
LibJf.LOGGER.info(pack.getName() + " opened " + type.name() + "/" + id.toString());
LibJf.LOGGER.info(pack.getInfo().title() + " opened " + type.name() + "/" + id.toString());
return null;
}
return previous;

View File

@ -5,11 +5,10 @@ plugins {
}
base {
archivesName.set("libjf-data-v0")
archivesName = "libjf-data-v0"
}
dependencies {
val fabricVersion: String by rootProject.extra
api(devProject(":libjf-base"))
include(fabricApi.module("fabric-resource-loader-v0", fabricVersion))
include("net.fabricmc.fabric-api:fabric-resource-loader-v0")
}

View File

@ -2,7 +2,9 @@ package io.gitlab.jfronny.libjf.data.mixin;
import io.gitlab.jfronny.libjf.data.Tags;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@ -37,8 +39,9 @@ public class EntityMixin {
private boolean libjf$opArmor() {
Entity entity = (Entity) (Object) this;
if (!(entity instanceof LivingEntity le)) return false;
AtomicInteger armorCount = new AtomicInteger();
for (ItemStack s : entity.getArmorItems()) {
for (ItemStack s : le.getArmorItems()) {
if (s.isIn(Tags.OVERPOWERED))
armorCount.getAndIncrement();
}

View File

@ -3,10 +3,9 @@ plugins {
}
base {
archivesName.set("libjf-mainhttp-v0")
archivesName = "libjf-mainhttp-v0"
}
dependencies {
val fabricVersion: String by rootProject.extra
implementation(fabricApi.module("fabric-api-base", fabricVersion))
modImplementation("net.fabricmc.fabric-api:fabric-api-base")
}

View File

@ -5,7 +5,7 @@ plugins {
}
base {
archivesName.set("libjf-translate-v1")
archivesName = "libjf-translate-v1"
}
dependencies {

View File

@ -31,7 +31,7 @@ public class GoogleTranslateService extends AbstractTranslateService<GoogleTrans
private GoogleTranslateService() throws URISyntaxException, IOException {
Map<String, GoogleTranslateLanguage> knownLanguages = new HashMap<>();
Matcher matcher = LANGUAGE_KEY.matcher(HttpClient.get("https://translate.google.com/m?mui=tl").sendString());
Matcher matcher = LANGUAGE_KEY.matcher(get("https://translate.google.com/m?mui=tl"));
while (matcher.find()) {
String id = matcher.group(1);
String name = matcher.group(2);
@ -49,9 +49,13 @@ public class GoogleTranslateService extends AbstractTranslateService<GoogleTrans
@Override
protected String performTranslate(String textToTranslate, GoogleTranslateLanguage translateFrom, GoogleTranslateLanguage translateTo) throws Exception {
String pageSource = "";
String pageSource;
try {
pageSource = getPageSource(textToTranslate, translateFrom.getIdentifier(), translateTo.getIdentifier());
} catch (Exception e) {
throw new TranslateException("Could not translate string", e);
}
try {
Matcher matcher = TRANSLATION_RESULT.matcher(pageSource);
if (matcher.find()) {
String match = matcher.group(1);
@ -62,7 +66,7 @@ public class GoogleTranslateService extends AbstractTranslateService<GoogleTrans
throw new TranslateException("Could not translate \"" + textToTranslate + "\": result page couldn't be parsed");
} catch (Exception e) {
try {
Path p = Files.createTempFile("translater-pagedump-", ".html").toAbsolutePath();
Path p = Files.createTempFile("libjf-translate-pagedump-", ".html").toAbsolutePath();
Files.writeString(p, pageSource);
throw new TranslateException("Could not translate string, see dumped page at " + p, e);
} catch (IOException ioe) {
@ -98,6 +102,61 @@ public class GoogleTranslateService extends AbstractTranslateService<GoogleTrans
return null;
String pageUrl = String.format("https://translate.google.com/m?hl=en&sl=%s&tl=%s&ie=UTF-8&prev=_m&q=%s",
translateFrom, translateTo, URLEncoder.encode(textToTranslate.trim(), StandardCharsets.UTF_8));
return HttpClient.get(pageUrl).sendString();
return get(pageUrl);
}
private static String get(String url) throws URISyntaxException, IOException {
return HttpClient.get(url).sendString();
}
// Alternative methods to get the page source (kept for reference)
// The HttpClient here is not the same as the one in libjf-commons, so adjust imports if you want to use them again
// private static String get(String url) {
// // An attempt to use the stuff wrapped by HttpClient. Will need to test and (if it works) adjust commons, so it can be used again
// try {
// HttpClient client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build();
// HttpResponse<String> response = client.send(HttpRequest.newBuilder().uri(new URI(url)).header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36").build(), HttpResponse.BodyHandlers.ofString());
// if (response.statusCode() / 100 != 2) {
// throw new IOException("Could not get page: " + response.statusCode());
// }
// return response.body();
// } catch (URISyntaxException | IOException | InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
// private static String get(String url) {
// // Technically, we should be using HttpClient, but Google Translate doesn't like it for some reason and this mess bypasses that
// // based on https://github.com/jhy/jsoup/blob/master/src/main/java/org/jsoup/helper/HttpConnection.java
// try {
// HttpsURLConnection connection = (HttpsURLConnection) URI.create(url).toURL().openConnection();
// connection.setRequestMethod("GET");
// connection.setInstanceFollowRedirects(false);
// connection.setConnectTimeout(5000);
// connection.setReadTimeout(2500);
// connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
// connection.connect();
// if (connection.getResponseCode() / 100 != 2) {
// String loc = connection.getHeaderField("Location");
// if (loc == null) loc = "";
// else loc = " (redirected to " + loc + ")";
// throw new IOException("Could not get page: " + connection.getResponseCode() + loc);
// }
// connection.getRequestMethod();
// connection.getURL();
// connection.getResponseCode();
// connection.getResponseMessage();
// connection.getContentType();
// try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
// StringBuilder sb = new StringBuilder();
// String line;
// while ((line = br.readLine()) != null) {
// sb.append(line).append('\n');
// }
// return sb.toString();
// }
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
}

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.libjf.translate.impl.libretranslate;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.gson.reflect.TypeToken;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
import io.gitlab.jfronny.libjf.translate.api.TranslateException;
import io.gitlab.jfronny.libjf.translate.impl.AbstractTranslateService;
import io.gitlab.jfronny.libjf.translate.impl.libretranslate.model.*;

View File

@ -20,7 +20,7 @@ public class TestEntrypoint implements ModInitializer {
}
{
LibreTranslateService ts = LibreTranslateService.get("https://translate.argosopentech.com");
LibreTranslateService ts = LibreTranslateService.get("https://libretranslate.com");
LibJf.LOGGER.info("Testing LibreTranslate");
final String sourceEN = "Hello, World!";
assertEqual("en", ts.detect(sourceEN).getIdentifier());
@ -33,6 +33,6 @@ public class TestEntrypoint implements ModInitializer {
private static void assertEqual(Object o1, Object o2) {
if (!Objects.equals(o1, o2))
throw new AssertionError("Assertion not met: expected " + o1 + " but got " + o2);
throw new RuntimeException("Assertion not met: expected " + o1 + " but got " + o2);
}
}

View File

@ -5,12 +5,11 @@ plugins {
}
base {
archivesName.set("libjf-unsafe-v0")
archivesName = "libjf-unsafe-v0"
}
dependencies {
val commonsVersion: String by rootProject.extra
api(devProject(":libjf-base"))
compileOnly("io.gitlab.jfronny:commons-unsafe:$commonsVersion") { isTransitive = false }
shadow("io.gitlab.jfronny:commons-unsafe:$commonsVersion") { isTransitive = false }
compileOnly(libs.commons.unsafe) { isTransitive = false }
shadow(libs.commons.unsafe) { isTransitive = false }
}

View File

@ -1,118 +0,0 @@
package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.commons.logging.Logger;
import net.fabricmc.loader.impl.util.log.Log;
import net.fabricmc.loader.impl.util.log.LogCategory;
public class FLLogger implements Logger {
private final LogCategory category;
public FLLogger(String context, String... names) {
this.category = LogCategory.createCustom(context, names);
}
@Override
public String getName() {
return category.name;
}
@Override
public void trace(String msg) {
Log.trace(category, msg);
}
@Override
public void trace(String format, Object arg) {
Log.trace(category, format, arg);
}
@Override
public void trace(String format, Object... args) {
Log.trace(category, format, args);
}
@Override
public void trace(String msg, Throwable t) {
Log.trace(category, msg, t);
}
@Override
public void debug(String msg) {
Log.debug(category, msg);
}
@Override
public void debug(String format, Object arg) {
Log.debug(category, format, arg);
}
@Override
public void debug(String format, Object... args) {
Log.debug(category, format, args);
}
@Override
public void debug(String msg, Throwable t) {
Log.debug(category, msg, t);
}
@Override
public void info(String msg) {
Log.info(category, msg);
}
@Override
public void info(String format, Object arg) {
Log.info(category, format, arg);
}
@Override
public void info(String format, Object... args) {
Log.info(category, format, args);
}
@Override
public void info(String msg, Throwable t) {
Log.info(category, msg, t);
}
@Override
public void warn(String msg) {
Log.warn(category, msg);
}
@Override
public void warn(String format, Object arg) {
Log.warn(category, format, arg);
}
@Override
public void warn(String format, Object... args) {
Log.warn(category, format, args);
}
@Override
public void warn(String msg, Throwable t) {
Log.warn(category, msg, t);
}
@Override
public void error(String msg) {
Log.error(category, msg);
}
@Override
public void error(String format, Object arg) {
Log.error(category, format, arg);
}
@Override
public void error(String format, Object... args) {
Log.error(category, format, args);
}
@Override
public void error(String msg, Throwable t) {
Log.error(category, msg, t);
}
}

View File

@ -1,9 +1,6 @@
package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.commons.logging.Level;
import io.gitlab.jfronny.commons.logging.Logger;
import io.gitlab.jfronny.libjf.LibJf;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.LanguageAdapter;
public class JfLanguageAdapter implements LanguageAdapter {
@ -11,8 +8,6 @@ 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 LibJf entrypoint
Logger.setMinimumLevel(FabricLoader.getInstance().isDevelopmentEnvironment() ? Level.TRACE : Level.INFO);
LibJf.LOGGER.info("Starting unsafe init"); // Also ensures LibJF.<clinit> is called and Gson is initialized
DynamicEntry.execute("libjf:preEarly", UltraEarlyInit.class, s -> s.instance().init());
DynamicEntry.execute("libjf:early", UltraEarlyInit.class, s -> s.instance().init());

View File

@ -5,15 +5,14 @@ plugins {
}
base {
archivesName.set("libjf-web-v1")
archivesName = "libjf-web-v1"
}
dependencies {
val fabricVersion: String by rootProject.extra
api(devProject(":libjf-base"))
api(devProject(":libjf-config-core-v2"))
api(devProject(":libjf-mainhttp-v0"))
include(modImplementation(fabricApi.module("fabric-command-api-v2", fabricVersion))!!)
include(modImplementation("net.fabricmc.fabric-api:fabric-command-api-v2")!!)
annotationProcessor(project(":libjf-config-compiler-plugin-v2"))
}

View File

@ -1,11 +1,13 @@
pluginManagement {
repositories {
maven("https://maven.fabricmc.net/") // FabricMC
// maven("https://maven.architectury.dev/") // Architectury
// maven("https://files.minecraftforge.net/maven/") // Forge
maven("https://maven.frohnmeyer-wds.de/artifacts") // scripts
gradlePluginPortal()
}
plugins {
id("jfmod") version("1.5-SNAPSHOT")
id("jfmod") version("1.6-SNAPSHOT")
id("jfmod.module")
}
}
@ -31,3 +33,6 @@ include("libjf-unsafe-v0")
include("libjf-mainhttp-v0")
include("libjf-web-v1")
include("libjf-bom")
include("libjf-catalog")