[*] Introduce Fabric helper and adjust a bunch of stuff
This commit is contained in:
21
tweed5-minecraft/fabric-helper/build.gradle.kts
Normal file
21
tweed5-minecraft/fabric-helper/build.gradle.kts
Normal file
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id("de.siphalor.tweed5.local-runtime-only")
|
||||
id("de.siphalor.tweed5.minecraft.mod.cross-version")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
modCompileOnly(fabricApi.module("fabric-networking-api-v1", mcLibs.versions.fabric.api.get()))
|
||||
compileOnly("de.siphalor.tweed5:tweed5-comment-loader-extension")
|
||||
compileOnly("de.siphalor.tweed5:tweed5-core")
|
||||
compileOnly("de.siphalor.tweed5:tweed5-default-extensions")
|
||||
compileOnly("de.siphalor.tweed5:tweed5-serde-extension")
|
||||
compileOnly("de.siphalor.tweed5:tweed5-serde-gson")
|
||||
|
||||
listOf("fabric-networking-api-v1", "fabric-lifecycle-events-v1").forEach {
|
||||
modTestmodImplementation(fabricApi.module(it, mcLibs.versions.fabric.api.get()))
|
||||
}
|
||||
testmodImplementation(project(":tweed5-bundle", configuration = "minecraftModElements"))
|
||||
testmodImplementation("de.siphalor.tweed5:tweed5-comment-loader-extension")
|
||||
testmodImplementation("de.siphalor.tweed5:tweed5-serde-hjson")
|
||||
testmodImplementation("de.siphalor.tweed5:tweed5-serde-gson")
|
||||
}
|
||||
2
tweed5-minecraft/fabric-helper/gradle.properties
Normal file
2
tweed5-minecraft/fabric-helper/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
module.name = Tweed 5 Fabric Helper
|
||||
module.description = A collection of utility classes to make working with Tweed 5 configurations easier on Fabric.
|
||||
@@ -0,0 +1,79 @@
|
||||
package de.siphalor.tweed5.fabric.helper.api;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.siphalor.tweed5.commentloaderextension.api.CommentLoaderExtension;
|
||||
import de.siphalor.tweed5.commentloaderextension.api.CommentPathProcessor;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||
import de.siphalor.tweed5.data.gson.GsonReader;
|
||||
import lombok.Builder;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
@CommonsLog
|
||||
@Builder
|
||||
public class FabricConfigCommentLoader {
|
||||
private final ConfigContainer<?> configContainer;
|
||||
private final String modId;
|
||||
/**
|
||||
* The prefix of the language keys, <b>without the trailing dot!</b>
|
||||
*/
|
||||
private final String prefix;
|
||||
/**
|
||||
* An optional suffix of the language keys
|
||||
*/
|
||||
private final @Nullable String suffix;
|
||||
|
||||
public void loadCommentsFromLanguageFile(String languageCode) {
|
||||
CommentLoaderExtension commentLoaderExtension = configContainer.extension(CommentLoaderExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException("CommentLoaderExtension not declared on config"));
|
||||
|
||||
String langFilePath = "assets/" + modId + "/lang/" + languageCode + ".json";
|
||||
|
||||
InputStream langInputStream = getClass().getClassLoader().getResourceAsStream(langFilePath);
|
||||
if (langInputStream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (TweedDataReader reader = new GsonReader(new JsonReader(new InputStreamReader(langInputStream)))) {
|
||||
commentLoaderExtension.loadComments(
|
||||
reader, new CommentPathProcessor() {
|
||||
@Override
|
||||
public MatchStatus matches(String path) {
|
||||
if (!path.startsWith(prefix)) {
|
||||
return MatchStatus.NO;
|
||||
} else if (path.length() == prefix.length()) {
|
||||
if (suffix != null && !path.endsWith(suffix)) {
|
||||
return MatchStatus.NO;
|
||||
}
|
||||
return MatchStatus.YES;
|
||||
} else if (path.charAt(prefix.length()) != '.') {
|
||||
return MatchStatus.NO;
|
||||
} else if (suffix != null && !path.endsWith(suffix)) {
|
||||
return MatchStatus.MAYBE_DEEPER;
|
||||
} else {
|
||||
return MatchStatus.YES;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String process(String path) {
|
||||
if (path.equals(prefix)) {
|
||||
return "";
|
||||
}
|
||||
path = path.substring(prefix.length() + 1);
|
||||
if (suffix != null) {
|
||||
path = path.substring(0, path.length() - suffix.length());
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to load comments from language file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package de.siphalor.tweed5.fabric.helper.api;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainerSetupPhase;
|
||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedSerde;
|
||||
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@CommonsLog
|
||||
public class FabricConfigContainerHelper<T extends @Nullable Object> {
|
||||
@Getter
|
||||
private final ConfigContainer<T> configContainer;
|
||||
private final ReadWriteExtension readWriteExtension;
|
||||
private final @Nullable PatchExtension patchExtension;
|
||||
private final TweedSerde serde;
|
||||
@Getter
|
||||
private final String modId;
|
||||
|
||||
private @Nullable Path tempConfigDirectory;
|
||||
|
||||
public static <T extends @Nullable Object> FabricConfigContainerHelper<T> create(
|
||||
ConfigContainer<T> configContainer,
|
||||
TweedSerde serde,
|
||||
String modId
|
||||
) {
|
||||
if (configContainer.setupPhase() != ConfigContainerSetupPhase.INITIALIZED) {
|
||||
throw new IllegalStateException(
|
||||
"Config container must be fully initialized before creating helper. "
|
||||
+ "Usually you're just missing a call to initialize()"
|
||||
);
|
||||
}
|
||||
return new FabricConfigContainerHelper<>(configContainer, serde, modId);
|
||||
}
|
||||
|
||||
private FabricConfigContainerHelper(ConfigContainer<T> configContainer, TweedSerde serde, String modId) {
|
||||
this.configContainer = configContainer;
|
||||
this.readWriteExtension = configContainer.extension(ReadWriteExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException("ReadWriteExtension not declared in config container"));
|
||||
this.patchExtension = configContainer.extension(PatchExtension.class).orElse(null);
|
||||
this.serde = serde;
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
public T loadAndUpdateInConfigDirectory(Supplier<T> defaultValueSupplier) {
|
||||
T configValue = readConfigInConfigDirectory(defaultValueSupplier);
|
||||
writeConfigInConfigDirectory(configValue);
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public void readPartialConfigInConfigDirectory(T value, Consumer<Patchwork> readContextCustomizer) {
|
||||
if (patchExtension == null) {
|
||||
throw new IllegalStateException(
|
||||
"PatchExtension must be declared in config container for partially loading config"
|
||||
);
|
||||
}
|
||||
|
||||
File configFile = getConfigFile();
|
||||
if (!configFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (TweedDataReader reader = serde.createReader(new FileInputStream(configFile))) {
|
||||
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
|
||||
readContextCustomizer.accept(contextExtensionsData);
|
||||
PatchInfo patchInfo = patchExtension.collectPatchInfo(contextExtensionsData);
|
||||
|
||||
T readValue = readWriteExtension.read(reader, configContainer().rootEntry(), contextExtensionsData);
|
||||
|
||||
patchExtension.patch(configContainer.rootEntry(), value, readValue, patchInfo);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed loading config file " + configFile.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public T readConfigInConfigDirectory(Supplier<T> defaultValueSupplier) {
|
||||
File configFile = getConfigFile();
|
||||
if (!configFile.exists()) {
|
||||
return defaultValueSupplier.get();
|
||||
}
|
||||
|
||||
try (TweedDataReader reader = serde.createReader(new FileInputStream(configFile))) {
|
||||
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
|
||||
return readWriteExtension.read(reader, configContainer.rootEntry(), contextExtensionsData);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed loading config file " + configFile.getAbsolutePath(), e);
|
||||
return defaultValueSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeConfigInConfigDirectory(T configValue) {
|
||||
File configFile = getConfigFile();
|
||||
Path tempConfigDirectory = getOrCreateTempConfigDirectory();
|
||||
File tempConfigFile = tempConfigDirectory.resolve(getConfigFileName()).toFile();
|
||||
try (TweedDataWriter writer = serde.createWriter(new FileOutputStream(tempConfigFile))) {
|
||||
Patchwork contextExtensionsData = readWriteExtension.createReadWriteContextExtensionsData();
|
||||
|
||||
readWriteExtension.write(
|
||||
writer,
|
||||
configValue,
|
||||
configContainer.rootEntry(),
|
||||
contextExtensionsData
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to write config file " + tempConfigFile.getAbsolutePath(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (configFile.exists()) {
|
||||
if (!configFile.delete()) {
|
||||
throw new IOException("Failed to overwrite old config file " + configFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
Files.move(tempConfigFile.toPath(), configFile.toPath());
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to move temporary config file " + tempConfigFile.getAbsolutePath() + " to " + configFile.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private File getConfigFile() {
|
||||
Path configDir = FabricLoader.getInstance().getConfigDir();
|
||||
configDir.toFile().mkdirs();
|
||||
return configDir.resolve(getConfigFileName()).toFile();
|
||||
}
|
||||
|
||||
private String getConfigFileName() {
|
||||
return modId + serde.getPreferredFileExtension();
|
||||
}
|
||||
|
||||
private Path getOrCreateTempConfigDirectory() {
|
||||
if (tempConfigDirectory == null) {
|
||||
try {
|
||||
tempConfigDirectory = Files.createTempDirectory("tweed5-config");
|
||||
tempConfigDirectory.toFile().deleteOnExit();
|
||||
return tempConfigDirectory;
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to create temporary config directory, using game directory instead");
|
||||
}
|
||||
tempConfigDirectory = FabricLoader.getInstance().getGameDir().resolve(".tweed5-tmp/").resolve(modId);
|
||||
tempConfigDirectory.toFile().mkdirs();
|
||||
}
|
||||
return tempConfigDirectory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NullMarked
|
||||
package de.siphalor.tweed5.fabric.helper.api;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,60 @@
|
||||
package de.siphalor.tweed5.fabric.helper.testmod;
|
||||
|
||||
import de.siphalor.tweed5.attributesextension.api.serde.filter.AttributesReadWriteFilterExtension;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonSerde;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonWriter;
|
||||
import de.siphalor.tweed5.fabric.helper.api.FabricConfigCommentLoader;
|
||||
import de.siphalor.tweed5.fabric.helper.api.FabricConfigContainerHelper;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.TweedPojoWeaverBootstrapper;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
|
||||
@CommonsLog
|
||||
public class FabricHelperTestMod implements ModInitializer {
|
||||
public static final String MOD_ID = "tweed5_fabric_helper_testmod";
|
||||
|
||||
private TestModConfig config;
|
||||
private ConfigContainer<TestModConfig> configContainer;
|
||||
private FabricConfigContainerHelper<TestModConfig> configContainerHelper;
|
||||
private AttributesReadWriteFilterExtension configFilterExtension;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
configContainer = TweedPojoWeaverBootstrapper.create(TestModConfig.class).weave();
|
||||
configContainer.extension(AttributesReadWriteFilterExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException("AttributesReadWriteFilterExtension not found"))
|
||||
.markAttributeForFiltering("reload");
|
||||
configFilterExtension = configContainer.extension(AttributesReadWriteFilterExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException("AttributesReadWriteFilterExtension not found"));
|
||||
|
||||
configContainer.initialize();
|
||||
|
||||
configContainerHelper = FabricConfigContainerHelper.create(
|
||||
configContainer,
|
||||
new HjsonSerde(new HjsonWriter.Options()),
|
||||
MOD_ID
|
||||
);
|
||||
FabricConfigCommentLoader.builder()
|
||||
.configContainer(configContainer)
|
||||
.modId(MOD_ID)
|
||||
.prefix(MOD_ID + ".config")
|
||||
.build()
|
||||
.loadCommentsFromLanguageFile("en_us");
|
||||
|
||||
config = configContainerHelper.loadAndUpdateInConfigDirectory(TestModConfig::new);
|
||||
|
||||
log.info("Hello " + config.helloStart() + config.helloEnd());
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(_server -> onServerStarted());
|
||||
}
|
||||
|
||||
private void onServerStarted() {
|
||||
configContainerHelper.readPartialConfigInConfigDirectory(config, patchwork ->
|
||||
configFilterExtension.addFilter(patchwork, "scope", "game")
|
||||
);
|
||||
|
||||
log.info("Hello " + config.helloInGame() + config.helloEnd());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.siphalor.tweed5.fabric.helper.testmod;
|
||||
|
||||
import de.siphalor.tweed5.attributesextension.api.AttributesExtension;
|
||||
import de.siphalor.tweed5.attributesextension.api.serde.filter.AttributesReadWriteFilterExtension;
|
||||
import de.siphalor.tweed5.commentloaderextension.api.CommentLoaderExtension;
|
||||
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.DefaultWeavingExtensions;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeavingExtension;
|
||||
import de.siphalor.tweed5.weaver.pojoext.attributes.api.Attribute;
|
||||
import de.siphalor.tweed5.weaver.pojoext.attributes.api.AttributesPojoWeavingProcessor;
|
||||
import de.siphalor.tweed5.weaver.pojoext.serde.api.auto.AutoReadWritePojoWeavingProcessor;
|
||||
import de.siphalor.tweed5.weaver.pojoext.serde.api.auto.DefaultReadWriteMappings;
|
||||
import lombok.Data;
|
||||
|
||||
@PojoWeaving(extensions = {
|
||||
CommentLoaderExtension.class,
|
||||
ReadWriteExtension.class,
|
||||
PatchExtension.class,
|
||||
AttributesExtension.class,
|
||||
AttributesReadWriteFilterExtension.class,
|
||||
})
|
||||
@PojoWeavingExtension(AutoReadWritePojoWeavingProcessor.class)
|
||||
@PojoWeavingExtension(AttributesPojoWeavingProcessor.class)
|
||||
@DefaultWeavingExtensions
|
||||
@DefaultReadWriteMappings
|
||||
@CompoundWeaving(namingFormat = "kebab_case")
|
||||
@Data
|
||||
public class TestModConfig {
|
||||
private String helloStart = "Minecraft";
|
||||
@Attribute(key = "scope", values = "game")
|
||||
private String helloInGame = "Game";
|
||||
private String helloEnd = "!";
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tweed5_fabric_helper_testmod.config.hello-start": "Whom to greet when the game starts",
|
||||
"tweed5_fabric_helper_testmod.config.hello-in-game": "Whom to greet when the player joins a game"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "tweed5_fabric_helper_testmod",
|
||||
"version": "0.1.0",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"de.siphalor.tweed5.fabric.helper.testmod.FabricHelperTestMod"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user