Merge branch 'refs/heads/breaking'
All checks were successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/jfmod Pipeline was successful

This commit is contained in:
Johannes Frohnmeyer 2024-04-23 21:06:21 +02:00
commit bd30200197
Signed by: Johannes
GPG Key ID: E76429612C2929F4
60 changed files with 791 additions and 488 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.5"
yarn = "build.1"
fabric-loader = "0.15.10"
fabric-api = "0.97.5+1.20.5"
jf-commons = "1.7-SNAPSHOT"
modmenu = "9.2.0-beta.2"
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,25 @@
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 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 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();
static {
HiddenAnnotationExclusionStrategy.register();
GsonHolders.registerSerializer();
Logger.setMinimumLevel(FabricLoader.getInstance().isDevelopmentEnvironment() ? Level.TRACE : Level.INFO);
Serializer.setInstance(new DatabindSerializer<>(JSON_TRANSPORT, MAPPER));
}
@Override
@ -24,10 +28,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,98 @@
package io.gitlab.jfronny.libjf.log;
import io.gitlab.jfronny.commons.StringFormatter;
import java.text.MessageFormat;
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 System.Logger 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;
System.Logger 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 {
if (params != null && params.length > 0) {
String[] strings = new String[params.length];
for (int i = 0; i < params.length; i++) {
strings[i] = StringFormatter.toString(params[i]);
}
message = new MessageFormat(message).format(strings);
}
logger.log(level, bundle, message, thrown);
}
}
}

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,116 @@
package io.gitlab.jfronny.libjf.log;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.commons.logger.CompactLogger;
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, (Object[]) null);
}
@Override
public void log(Level level, String message, Throwable throwable) {
log(level, null, message, throwable, (Object[]) null);
}
@Override
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
log(level, bundle, msg, thrown, (Object[]) null);
}
@Override
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
log(level, bundle, format, null, params);
}
private 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 = CompactLogger.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,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

@ -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

@ -14,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

@ -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;

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,6 +1,7 @@
package io.gitlab.jfronny.libjf.config.api.v2;
import io.gitlab.jfronny.gson.stream.*;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.impl.dsl.DslEntryInfo;
import org.jetbrains.annotations.ApiStatus;
@ -68,13 +69,13 @@ 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;
<TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void loadFromJson(Reader reader) throws TEx, IllegalAccessException;
/**
* Write the currently cached value to the writer
* @param writer The writer to write to
*/
void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException;
<TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void writeTo(Writer writer, String translationPrefix) throws TEx, IllegalAccessException;
/**
* @return Get the width for this entry

View File

@ -1,10 +1,10 @@
package io.gitlab.jfronny.libjf.config.api.v2.dsl;
import io.gitlab.jfronny.gson.stream.JsonReader;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import java.io.IOException;
@FunctionalInterface
public interface Migration {
void apply(JsonReader reader) throws IOException;
<TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void apply(Reader reader) throws IOException;
}

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,12 +1,14 @@
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;
import io.gitlab.jfronny.libjf.config.impl.watch.JfConfigWatchService;
import io.gitlab.jfronny.libjf.serialize.SerializationMode;
import java.io.*;
import java.nio.file.Files;
@ -19,7 +21,8 @@ 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 = new JsonReader(br)) {
SerializationMode.asConfig(jr);
runActions(id, createActions(c), jr);
} catch (Exception e) {
LibJf.LOGGER.error("Could not read config for " + id, e);
@ -29,21 +32,21 @@ 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)) {
LibJf.LOGGER.warn("Unrecognized key in config for " + id + ": " + name);
@ -61,8 +64,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 +74,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 +94,8 @@ 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 = new JsonWriter(bw)) {
SerializationMode.asConfig(jw);
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,19 @@
package io.gitlab.jfronny.libjf.config.impl.dsl;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.commons.Serializer;
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;
@ -127,38 +130,42 @@ 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>> void loadFromJson(Reader reader) throws TEx, IllegalAccessException {
var next = reader.peek();
if (type.isBool()) {
if (next == JsonToken.BOOLEAN) setUnchecked(reader.nextBoolean());
if (next == Token.BOOLEAN) setUnchecked(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) {
if (next == Token.STRING || next == Token.NUMBER) setUnchecked(reader.nextString());
else if (next == Token.BOOLEAN) setUnchecked(Boolean.toString(reader.nextBoolean()));
else if (next == Token.NULL) {
reader.nextNull();
setUnchecked(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) setUnchecked(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) setUnchecked(reader.nextLong());
else LibJf.LOGGER.error("Unexpected value for " + name + ": expected number but got " + next);
} else if (type.isDouble()) {
if (next == JsonToken.NUMBER) {
if (next == Token.NUMBER) {
setUnchecked(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) setUnchecked((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) setUnchecked(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 {
setValue((T) LibJf.MAPPER.getAdapter(TypeToken.get(type.asClass())).deserialize(reader));
} catch (MalformedDataException e) {
LibJf.LOGGER.error("Could not read " + name, e);
}
}
}
@ -169,7 +176,7 @@ public class DslEntryInfo<T> implements EntryInfo<T> {
}
@Override
public void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException {
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void writeTo(Writer writer, String translationPrefix) throws TEx, IllegalAccessException {
T value = getValue();
String commentText;
if ((commentText = JfConfigSafe.TRANSLATION_SUPPLIER.apply(translationPrefix + getName() + ".tooltip")) != null) {
@ -179,7 +186,11 @@ public class DslEntryInfo<T> implements EntryInfo<T> {
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);
try {
LibJf.MAPPER.getAdapter((TypeToken<T>) TypeToken.get(Objects.requireNonNullElse(type.asClass(), String.class))).serialize(value, writer);
} catch (MalformedDataException e) {
LibJf.LOGGER.error("Could not write " + name, e);
}
}
@Override

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,25 @@
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 net.minecraft.util.Identifier;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
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 record ConfigurationCompletePacket() implements CustomPayload {
public static final CustomPayload.Id<ConfigurationCompletePacket> ID = CustomPayload.id(RequestRouter.MOD_ID + ":handshake_complete");
public static final PacketCodec<ByteBuf, ConfigurationCompletePacket> CODEC = new PacketCodec<>() {
@Override
public ConfigurationCompletePacket decode(ByteBuf buf) {
return new ConfigurationCompletePacket();
}
public ConfigurationCompletePacket(PacketByteBuf buf) {
this();
}
@Override
public void encode(ByteBuf buf, ConfigurationCompletePacket value) {
}
};
@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,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 net.minecraft.util.Identifier;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
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 = CustomPayload.id(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,33 @@
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.packet.CustomPayload;
import org.jetbrains.annotations.Nullable;
public record RequestPacket(long request, @Nullable Long parent, String name, PacketByteBuf aux) implements CustomPayload {
public static final Id<RequestPacket> ID = CustomPayload.id(RequestRouter.MOD_ID + ":request");
public static final PacketCodec<PacketByteBuf, RequestPacket> CODEC = PacketCodec.of(
(value, buf) -> {
buf.writeLong(value.request);
buf.writeBoolean(value.parent != null);
if (value.parent != null) buf.writeLong(value.parent);
buf.writeString(value.name);
buf.writeBytes(value.aux);
},
buf -> {
long request = buf.readLong();
Long parent = buf.readBoolean() ? buf.readLong() : null;
String name = buf.readString();
PacketByteBuf aux = PacketByteBufs.copy(buf);
return new RequestPacket(request, parent, name, aux);
}
);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View File

@ -0,0 +1,28 @@
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.packet.CustomPayload;
public record ResponsePacket(long request, int status, PacketByteBuf aux) implements CustomPayload {
public static final CustomPayload.Id<ResponsePacket> ID = CustomPayload.id(RequestRouter.MOD_ID + ":response");
public static final PacketCodec<PacketByteBuf, ResponsePacket> CODEC = PacketCodec.of(
(value, buf) -> {
buf.writeLong(value.request());
buf.writeInt(value.status());
buf.writeBytes(value.aux());
},
buf -> {
long request = buf.readLong();
int status = buf.readInt();
return new ResponsePacket(request, status, PacketByteBufs.copy(buf));
}
);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View File

@ -1,18 +1,14 @@
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.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 +30,13 @@ 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
@ -51,7 +47,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,18 +1,24 @@
package io.gitlab.jfronny.libjf.config.impl.ui.tiny;
import io.gitlab.jfronny.commons.serialize.gson.api.v2.GsonHolders;
import io.gitlab.jfronny.commons.Serializer;
import io.gitlab.jfronny.commons.serialize.Transport;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
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;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.CategoryBuilder;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.api.v2.ui.ConfigScreenFactory;
import io.gitlab.jfronny.libjf.serialize.SerializationMode;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
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
@ -26,8 +32,9 @@ public class TinyConfigScreenFactory implements ConfigScreenFactory<Screen, Tiny
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) {
var value = entry.getValue();
jsonified = LibJf.JSON_TRANSPORT.write(writer -> LibJf.MAPPER.serialize(value, SerializationMode.asConfig(writer)));
} catch (IllegalAccessException | IOException e) {
throw new RuntimeException(e);
}
String key = config.getTranslationPrefix() + entry.getName();
@ -38,7 +45,12 @@ public class TinyConfigScreenFactory implements ConfigScreenFactory<Screen, Tiny
jsonified,
json -> {
try {
entry.setValue(GsonHolders.CONFIG.getGson().fromJson(json, type.asClass()));
entry.setValue(LibJf.JSON_TRANSPORT.read(
json,
(Transport.Returnable<JsonReader, ? extends Object, IOException>) reader -> LibJf.MAPPER
.getAdapter(TypeToken.get(type.asClass()))
.deserialize(SerializationMode.asConfig(reader))));
entry.setValue(Serializer.getInstance().deserialize(json, type.asClass()));
config.write();
} catch (Throwable e) {
LibJf.LOGGER.error("Could not write element", e);

View File

@ -1,14 +1,19 @@
package io.gitlab.jfronny.libjf.config.impl.ui.tiny.entry;
import io.gitlab.jfronny.commons.Serializer;
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.databind.api.TypeToken;
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;
import io.gitlab.jfronny.libjf.config.api.v2.EntryInfo;
import io.gitlab.jfronny.libjf.config.api.v2.JfConfig;
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
import io.gitlab.jfronny.libjf.config.impl.ConfigCore;
import io.gitlab.jfronny.libjf.config.impl.ui.tiny.EditorScreen;
import io.gitlab.jfronny.libjf.serialize.SerializationMode;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.widget.ButtonWidget;
@ -19,6 +24,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 +177,7 @@ public class EntryInfoWidgetBuilder {
final String jsonified;
if (state.tempValue == null) {
try {
jsonified = GsonHolders.CONFIG.getGson().toJson(state.cachedValue);
jsonified = LibJf.JSON_TRANSPORT.write(writer -> LibJf.MAPPER.serialize(state.cachedValue, SerializationMode.asConfig(writer)));
} catch (Throwable e) {
LibJf.LOGGER.error("Could not stringify element", e);
SystemToast.add(
@ -193,7 +199,11 @@ public class EntryInfoWidgetBuilder {
jsonified,
json -> {
try {
state.updateCache(GsonHolders.CONFIG.getGson().fromJson(json, info.getValueType().asClass()));
state.updateCache(LibJf.JSON_TRANSPORT.read(
json,
(Transport.Returnable<JsonReader, ? extends T, IOException>) reader -> LibJf.MAPPER
.getAdapter((TypeToken<T>) TypeToken.get(info.getValueType().asClass()))
.deserialize(SerializationMode.asConfig(reader))));
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

@ -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,15 @@ 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() {
return DISABLED.orElse(false);
}
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

@ -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

@ -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")
}
}