feat(config-network): implement relevant config abstractions over network and add clientside GUI for server config
This commit is contained in:
parent
1f9da79302
commit
172d4d5b79
|
@ -38,11 +38,11 @@ allprojects {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
// Temporarily disabled since modmenu doesn't support snapshots
|
||||
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") }
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import net.minecraft.server.command.ServerCommandSource;
|
|||
import net.minecraft.text.MutableText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Language;
|
||||
import org.apache.commons.codec.language.bm.Lang;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
@ -111,6 +110,7 @@ public class JfConfigCommand implements ModInitializer {
|
|||
});
|
||||
|
||||
private <T> void registerEntry(ConfigCategory config, String subpath, LiteralArgumentBuilder<ServerCommandSource> cns, EntryInfo<T> entry) {
|
||||
if (!entry.supportsRepresentation()) return;
|
||||
LiteralArgumentBuilder<ServerCommandSource> c_entry = literal(entry.getName()).executes(context -> {
|
||||
String msg = "The value of " + subpath + "." + entry.getName() + " is ";
|
||||
Text visualized = visualizeOption(config, entry, tryRun(entry::getValue));
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package io.gitlab.jfronny.libjf.config.api.v2.ui;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.ConfigInstance;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.dsl.DSL;
|
||||
import io.gitlab.jfronny.libjf.config.impl.ui.ConfigScreenFactoryDiscovery;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public interface ConfigScreenFactory<S extends Screen, B extends ConfigScreenFactory.Built<S>> {
|
||||
static ConfigScreenFactory<?, ?> getInstance() {
|
||||
return ConfigScreenFactoryDiscovery.getConfigured2();
|
||||
|
@ -13,6 +17,19 @@ public interface ConfigScreenFactory<S extends Screen, B extends ConfigScreenFac
|
|||
|
||||
int getPriority();
|
||||
|
||||
default Screen createOverview(Screen parent) {
|
||||
return createOverview(ConfigHolder.getInstance(), parent);
|
||||
}
|
||||
|
||||
default Screen createOverview(ConfigHolder holder, Screen parent) {
|
||||
return create(DSL.create("overview").config(builder -> {
|
||||
Objects.requireNonNull(holder).getRegistered().forEach((n, ci) -> {
|
||||
builder.referenceConfig(ci);
|
||||
});
|
||||
return builder;
|
||||
}), parent).get();
|
||||
}
|
||||
|
||||
interface Built<S extends Screen> {
|
||||
S get();
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"libjf-config-core-v2.jfconfig.title": "LibJF Config",
|
||||
"libjf-config-core-v2.jfconfig.watchForChanges": "Watch for changes",
|
||||
"libjf-config-core-v2.jfconfig.watchForChanges.tooltip": "Automatically reload configs when they are changed",
|
||||
"libjf-config-core-v2.errored_entry": "Could not render entry: %s",
|
||||
"libjf-config-core-v2.presets": "Presets",
|
||||
"libjf-config-core-v2.default": "Default",
|
||||
"libjf-config-core-v2.see-also": "See also: %s",
|
||||
|
|
|
@ -51,6 +51,14 @@ public interface EntryInfo<T> {
|
|||
*/
|
||||
Type getValueType();
|
||||
|
||||
/**
|
||||
* Whether the entry can be represented. If this is false, all methods except getName, fix and reset must throw UnsupportedOperationExceptions
|
||||
* @return Whether this entry can be represented
|
||||
*/
|
||||
default boolean supportsRepresentation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the current value is within expected bounds.
|
||||
*/
|
||||
|
|
|
@ -162,7 +162,7 @@ public sealed interface Type {
|
|||
}
|
||||
}
|
||||
|
||||
final record TEnum<T>(@Nullable Class<T> klazz, String name, T[] options) implements Type {
|
||||
record TEnum<T>(@Nullable Class<T> klazz, String name, T[] options) implements Type {
|
||||
public TEnum(Class<T> klazz) {
|
||||
this(klazz, klazz.getSimpleName(), klazz.getEnumConstants());
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ public sealed interface Type {
|
|||
}
|
||||
}
|
||||
|
||||
final record TUnknown(java.lang.reflect.Type klazz) implements Type {
|
||||
record TUnknown(java.lang.reflect.Type klazz) implements Type {
|
||||
@Override
|
||||
public @Nullable java.lang.reflect.Type asClass() {
|
||||
return klazz;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import io.gitlab.jfronny.scripts.*
|
||||
|
||||
plugins {
|
||||
id("jfmod.module")
|
||||
}
|
||||
|
||||
base {
|
||||
archivesName.set("libjf-config-network-v0")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val fabricVersion: String by rootProject.extra
|
||||
api(devProject(":libjf-base"))
|
||||
api(devProject(":libjf-config-core-v2"))
|
||||
include(modImplementation(fabricApi.module("fabric-networking-api-v1", fabricVersion))!!)
|
||||
include(modImplementation(fabricApi.module("fabric-command-api-v2", fabricVersion))!!)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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 net.fabricmc.fabric.api.client.networking.v1.*;
|
||||
|
||||
public class JfConfigNetworkClient {
|
||||
public static boolean isAvailable = false;
|
||||
public static void initialize() {
|
||||
JfConfigNetworkCommands.initialize();
|
||||
ClientPlayNetworking.registerGlobalReceiver(RequestRouter.RESPONSE_ID, (client, handler, buf, responseSender) -> {
|
||||
RequestRouter.acceptResponse(buf, responseSender);
|
||||
});
|
||||
ClientPlayNetworking.registerGlobalReceiver(RequestRouter.REQUEST_ID, (client, handler, buf, responseSender) -> {
|
||||
RequestRouter.acceptRequest(buf, responseSender);
|
||||
});
|
||||
ClientConfigurationNetworking.registerGlobalReceiver(ConfigurationPacket.PACKET_TYPE, (packet, responseSender) -> {
|
||||
isAvailable = packet.version() == RequestRouter.PROTOCOL_VERSION; // Handshake possible?
|
||||
responseSender.sendPacket(new ConfigurationCompletePacket());
|
||||
});
|
||||
ClientConfigurationConnectionEvents.INIT.register((handler, client) -> {
|
||||
isAvailable = false; // Reset for new server connection
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.client;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.ui.ConfigScreenFactory;
|
||||
import io.gitlab.jfronny.libjf.config.impl.network.RequestRouter;
|
||||
import io.gitlab.jfronny.libjf.config.impl.network.rci.MirrorConfigHolder;
|
||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
|
||||
|
||||
public class JfConfigNetworkCommands {
|
||||
public static void initialize() {
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||
dispatcher.register(literal(RequestRouter.MOD_ID).executes(ctx -> {
|
||||
if (JfConfigNetworkClient.isAvailable) {
|
||||
ConfigHolder ch = new MirrorConfigHolder(ClientPlayNetworking.getSender());
|
||||
Screen screen = ConfigScreenFactory.getInstance().createOverview(ch, null);
|
||||
// Delay since the chat needs to close first
|
||||
MinecraftClient.getInstance().setScreen(new ScheduledScreen(screen));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
} else {
|
||||
ctx.getSource().sendError(Text.literal(RequestRouter.MOD_ID + " server is unavailable"));
|
||||
return -1;
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.client;
|
||||
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
public class ScheduledScreen extends Screen {
|
||||
private Screen scheduled;
|
||||
|
||||
protected ScheduledScreen(Screen scheduled) {
|
||||
super(Text.literal("Close this screen"));
|
||||
|
||||
this.scheduled = scheduled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed() {
|
||||
if (scheduled != null) {
|
||||
Screen s = scheduled;
|
||||
client.send(() -> client.setScreen(s));
|
||||
}
|
||||
scheduled = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface FollowupSender {
|
||||
void sendFollowupRequest(PacketSender responseSender, String name, PacketByteBuf body, ResponseHandler responseHandler, Map<String, RequestHandler> temporaryHandlers);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public record PMResponseHandler(Consumer<Response> handler) implements ResponseHandler {
|
||||
public PMResponseHandler {
|
||||
Objects.requireNonNull(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(PacketByteBuf buf) {
|
||||
handler.accept(new Response.Success(buf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeny() {
|
||||
handler.accept(Response.SimpleFailure.DENY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotFound() {
|
||||
handler.accept(Response.SimpleFailure.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String message) {
|
||||
handler.accept(new Response.Failure(message));
|
||||
}
|
||||
|
||||
public sealed interface Response {
|
||||
PacketByteBuf get();
|
||||
|
||||
record Success(PacketByteBuf buf) implements Response {
|
||||
@Override
|
||||
public PacketByteBuf get() {
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
enum SimpleFailure implements Response {
|
||||
DENY, NOT_FOUND;
|
||||
|
||||
@Override
|
||||
public PacketByteBuf get() {
|
||||
if (this == DENY) throw new RuntimeException("Access not allowed");
|
||||
else throw new UnsupportedOperationException("Method not found");
|
||||
}
|
||||
}
|
||||
|
||||
record Failure(String message) implements Response {
|
||||
public Failure {
|
||||
Objects.requireNonNull(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketByteBuf get() {
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
public interface RequestHandler {
|
||||
PacketByteBuf handle(PacketByteBuf buf, FollowupSender followupSender) throws Throwable;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
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());
|
||||
if (request != null) {
|
||||
switch (buf.readInt()) {
|
||||
case 0 -> request.responseHandler.onSuccess(buf);
|
||||
case 1 -> request.responseHandler.onNotFound();
|
||||
case 2 -> request.responseHandler.onDeny();
|
||||
case 3 -> request.responseHandler.onFailure(buf.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);
|
||||
try {
|
||||
if (buf.readBoolean()) {
|
||||
// persistent
|
||||
handleRequest(resp, id, buf, persistendHandlers.get(buf.readString()));
|
||||
} else {
|
||||
// followup
|
||||
Request parent = currentRequests.get(buf.readLong());
|
||||
if (parent == null) {
|
||||
resp.writeInt(1);
|
||||
} else {
|
||||
String key = buf.readString();
|
||||
RequestHandler handler = parent.temporaryHandlers.get(key);
|
||||
if (handler == null) handler = persistendHandlers.get(key);
|
||||
handleRequest(resp, id, buf, handler);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LibJf.LOGGER.error("Cannot complete request", t);
|
||||
resp.writeInt(3);
|
||||
resp.writeString(t.getMessage() == null ? "null" : t.getMessage());
|
||||
} finally {
|
||||
responseSender.sendPacket(RESPONSE_ID, resp);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleRequest(PacketByteBuf resp, long id, PacketByteBuf buf, RequestHandler handler) throws Throwable {
|
||||
if (handler == null) {
|
||||
resp.writeInt(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());
|
||||
}
|
||||
}
|
||||
|
||||
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 registerHandler(String name, RequestHandler handler) {
|
||||
persistendHandlers.put(name, handler);
|
||||
}
|
||||
|
||||
public static void sendRequest(PacketSender responseSender, String name, PacketByteBuf body, ResponseHandler responseHandler, Map<String, RequestHandler> temporaryHandlers) {
|
||||
sendRequest(responseSender, null, name, body, responseHandler, temporaryHandlers);
|
||||
}
|
||||
|
||||
private static void sendRequest(PacketSender responseSender, Long parent, String name, PacketByteBuf body, ResponseHandler responseHandler, Map<String, RequestHandler> temporaryHandlers) {
|
||||
long id;
|
||||
synchronized (currentRequests) {
|
||||
Set<Long> keys = currentRequests.keySet();
|
||||
do {
|
||||
id = random.nextLong();
|
||||
} 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);
|
||||
}
|
||||
|
||||
private record Request(Map<String, RequestHandler> temporaryHandlers, ResponseHandler responseHandler) {}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network;
|
||||
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
public interface ResponseHandler {
|
||||
void onSuccess(PacketByteBuf buf);
|
||||
void onDeny();
|
||||
void onNotFound();
|
||||
void onFailure(String message);
|
||||
}
|
|
@ -0,0 +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;
|
||||
|
||||
public record ConfigurationCompletePacket() implements FabricPacket {
|
||||
public static final Identifier ID = new Identifier(RequestRouter.MOD_ID, "handshake_complete");
|
||||
public static final PacketType<ConfigurationCompletePacket> PACKET_TYPE = PacketType.create(ID, ConfigurationCompletePacket::new);
|
||||
|
||||
public ConfigurationCompletePacket(PacketByteBuf buf) {
|
||||
this();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return PACKET_TYPE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(PacketByteBuf buf) {
|
||||
buf.writeInt(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return PACKET_TYPE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.rci;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.*;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.network.rci.entry.*;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MirrorConfigCategory extends MirrorObject implements ConfigCategory {
|
||||
protected final String id;
|
||||
protected final String categoryPath;
|
||||
protected final Supplier<MirrorConfigInstance> root;
|
||||
|
||||
public MirrorConfigCategory(PacketSender packetSender, String id, String categoryPath, Supplier<MirrorConfigInstance> root) {
|
||||
super(packetSender);
|
||||
this.id = id;
|
||||
this.categoryPath = categoryPath;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategoryPath() {
|
||||
return categoryPath;
|
||||
}
|
||||
|
||||
public void writeCategoryPath(PacketByteBuf buf) {
|
||||
root.get().writeConfigInstance(buf);
|
||||
buf.writeCollection(getCategoryPathList(), PacketByteBuf::writeString);
|
||||
}
|
||||
|
||||
protected List<String> getCategoryPathList() {
|
||||
if (getCategoryPath().isEmpty()) return List.of();
|
||||
String[] sc = getCategoryPath().split("\\.");
|
||||
return Arrays.stream(sc).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EntryInfo<?>> getEntries() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writeCategoryPath(buf);
|
||||
buf = sendRequest("getEntries", buf);
|
||||
return List.copyOf(buf.readList(b -> {
|
||||
String name = b.readString();
|
||||
int type = b.readInt();
|
||||
if (type == -1) return new MirrorEntryInfoUnsupported<>(packetSender, this, name);
|
||||
Type t = switch (Datatype.values()[type]) {
|
||||
case INT -> Type.TInt.INSTANCE;
|
||||
case LONG -> Type.TLong.INSTANCE;
|
||||
case FLOAT -> Type.TFloat.INSTANCE;
|
||||
case DOUBLE -> Type.TDouble.INSTANCE;
|
||||
case STRING -> Type.TString.INSTANCE;
|
||||
case BOOL -> Type.TBool.INSTANCE;
|
||||
case ENUM -> Type.TEnum.create(name, b.readList(PacketByteBuf::readString).toArray(String[]::new));
|
||||
};
|
||||
return new MirrorEntryInfo<>(
|
||||
packetSender,
|
||||
this,
|
||||
name,
|
||||
MirrorEntryInfo.read(b, t),
|
||||
t,
|
||||
b.readInt(),
|
||||
b.readDouble(),
|
||||
b.readDouble());
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Runnable> getPresets() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writeCategoryPath(buf);
|
||||
buf = sendRequest("getPresets", buf);
|
||||
return buf.readList(PacketByteBuf::readString).stream().collect(Collectors.toUnmodifiableMap(
|
||||
Function.identity(),
|
||||
s -> () -> runPreset(s)
|
||||
));
|
||||
}
|
||||
|
||||
private void runPreset(String id) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writeCategoryPath(buf);
|
||||
buf.writeString(id);
|
||||
sendRequest("runPreset", buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConfigInstance> getReferencedConfigs() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writeCategoryPath(buf);
|
||||
buf = sendRequest("getReferencedConfigs", buf);
|
||||
return buf.readList(b -> b.readBoolean()
|
||||
? MirrorConfigInstance.create(packetSender, b.readString())
|
||||
: MirrorConfigInstance.create(
|
||||
packetSender,
|
||||
b.readString(),
|
||||
Stream.concat(
|
||||
root.get().streamParentPaths(),
|
||||
Stream.of(
|
||||
Stream.concat(
|
||||
Stream.of(root.get().getId()),
|
||||
getCategoryPathList().stream()
|
||||
).toList()
|
||||
)
|
||||
).toList()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigCategory> getCategories() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writeCategoryPath(buf);
|
||||
buf = sendRequest("getCategories", buf);
|
||||
return buf.readList(PacketByteBuf::readString)
|
||||
.stream()
|
||||
.collect(Collectors.toUnmodifiableMap(
|
||||
Function.identity(),
|
||||
s -> new MirrorConfigCategory(packetSender, s, categoryPath + s + ".", root)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigInstance getRoot() {
|
||||
return root.get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.rci;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.ConfigHolder;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.ConfigInstance;
|
||||
import net.fabricmc.fabric.api.networking.v1.*;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.*;
|
||||
|
||||
public class MirrorConfigHolder extends MirrorObject implements ConfigHolder {
|
||||
public MirrorConfigHolder(PacketSender packetSender) {
|
||||
super(packetSender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String modId, ConfigInstance config) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ConfigInstance> getRegistered() {
|
||||
PacketByteBuf buf = sendRequest("getRegistered", null);
|
||||
return buf.readList(PacketByteBuf::readString)
|
||||
.stream()
|
||||
.collect(Collectors.toUnmodifiableMap(Function.identity(), s -> MirrorConfigInstance.create(packetSender, s)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigInstance get(String modId) {
|
||||
return isRegistered(modId) ? MirrorConfigInstance.create(packetSender, modId) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigInstance get(Path configPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String modId) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeString(modId);
|
||||
buf = sendRequest("isRegistered", buf);
|
||||
return buf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(Path configPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrateFiles(String modId) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
buf.writeString(modId);
|
||||
sendRequest("migrateFiles", buf);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.rci;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.*;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MirrorConfigInstance extends MirrorConfigCategory implements ConfigInstance {
|
||||
protected final List<List<String>> parentPaths;
|
||||
protected MirrorConfigInstance(PacketSender packetSender, String id, Supplier<MirrorConfigInstance> root, List<List<String>> parentPaths) {
|
||||
super(packetSender, id, "", root);
|
||||
this.parentPaths = parentPaths;
|
||||
}
|
||||
|
||||
public static MirrorConfigInstance create(PacketSender packetSender, String id) {
|
||||
return create(packetSender, id, List.of());
|
||||
}
|
||||
|
||||
public static MirrorConfigInstance create(PacketSender packetSender, String id, List<List<String>> parentPaths) {
|
||||
MirrorConfigInstance[] cis = {null};
|
||||
cis[0] = new MirrorConfigInstance(packetSender, id, () -> cis[0], parentPaths);
|
||||
return cis[0];
|
||||
}
|
||||
|
||||
public Stream<List<String>> streamParentPaths() {
|
||||
return parentPaths.stream();
|
||||
}
|
||||
|
||||
public void writeConfigInstance(PacketByteBuf buf) {
|
||||
buf.writeCollection(parentPaths, (b, l) -> b.writeCollection(l, PacketByteBuf::writeString));
|
||||
buf.writeString(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writeConfigInstance(buf);
|
||||
sendRequest("load", buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writeConfigInstance(buf);
|
||||
sendRequest("write", buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getFilePath() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.rci;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.impl.network.*;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MirrorObject {
|
||||
protected final PacketSender packetSender;
|
||||
|
||||
public MirrorObject(PacketSender packetSender) {
|
||||
this.packetSender = packetSender;
|
||||
}
|
||||
|
||||
protected <T> T synchronize(Consumer<Hold<T>> action) {
|
||||
Object[] result = {null};
|
||||
AtomicBoolean pause = new AtomicBoolean(true);
|
||||
action.accept(with -> {
|
||||
result[0] = with;
|
||||
pause.set(false);
|
||||
synchronized (result) {
|
||||
result.notifyAll();
|
||||
}
|
||||
});
|
||||
synchronized (result) {
|
||||
while (pause.get()) {
|
||||
try {
|
||||
if (pause.get()) result.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
return (T) result[0];
|
||||
}
|
||||
|
||||
protected PacketByteBuf sendRequest(String name, PacketByteBuf body) {
|
||||
return sendRequest(name, body, Map.of());
|
||||
}
|
||||
|
||||
protected PacketByteBuf sendRequest(String name, PacketByteBuf body, Map<String, RequestHandler> temporaryHandlers) {
|
||||
return this.<PMResponseHandler.Response>synchronize(hold -> {
|
||||
RequestRouter.sendRequest(packetSender, name, body, new PMResponseHandler(hold::resume), temporaryHandlers);
|
||||
}).get();
|
||||
}
|
||||
|
||||
protected interface Hold<T> {
|
||||
void resume(T with);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.rci.entry;
|
||||
|
||||
public enum Datatype {
|
||||
INT, LONG, FLOAT, DOUBLE, STRING, BOOL, ENUM
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.rci.entry;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.network.rci.MirrorConfigCategory;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
|
||||
public class MirrorEntryInfo<T> extends MirrorEntryInfoBase<T> {
|
||||
private final T defaultValue;
|
||||
private final Type valueType;
|
||||
private final int width;
|
||||
private final double minValue;
|
||||
private final double maxValue;
|
||||
|
||||
public MirrorEntryInfo(
|
||||
PacketSender packetSender,
|
||||
MirrorConfigCategory category,
|
||||
String entryName,
|
||||
T defaultValue,
|
||||
Type valueType,
|
||||
int width,
|
||||
double minValue,
|
||||
double maxValue
|
||||
) {
|
||||
super(packetSender, category, entryName);
|
||||
this.defaultValue = defaultValue;
|
||||
this.valueType = valueType;
|
||||
this.width = width;
|
||||
this.minValue = minValue;
|
||||
this.maxValue = maxValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDefault() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinValue() {
|
||||
return minValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
public static Object read(PacketByteBuf buf, Type type) {
|
||||
if (!buf.readBoolean()) return null;
|
||||
if (type.isInt()) return buf.readInt();
|
||||
if (type.isLong()) return buf.readLong();
|
||||
if (type.isFloat()) return buf.readFloat();
|
||||
if (type.isDouble()) return buf.readDouble();
|
||||
if (type.isString()) return buf.readString();
|
||||
if (type.isBool()) return buf.readBoolean();
|
||||
if (type.isEnum()) return type.asEnum().optionForString(buf.readString());
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static void write(PacketByteBuf buf, Type type, Object data) {
|
||||
if (data == null) buf.writeBoolean(false);
|
||||
else {
|
||||
buf.writeBoolean(true);
|
||||
if (type.isInt()) buf.writeInt((int) data);
|
||||
else if (type.isLong()) buf.writeLong((long) data);
|
||||
else if (type.isFloat()) buf.writeFloat((float) data);
|
||||
else if (type.isDouble()) buf.writeDouble((double) data);
|
||||
else if (type.isString()) buf.writeString((String) data);
|
||||
else if (type.isBool()) buf.writeBoolean((boolean) data);
|
||||
else if (type.isEnum()) buf.writeString(data.toString());
|
||||
else throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValue() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writePath(buf);
|
||||
buf = sendRequest("getEntryValue", buf);
|
||||
return (T) read(buf, valueType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T value) {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writePath(buf);
|
||||
write(buf, valueType, value);
|
||||
sendRequest("setEntryValue", buf);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
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.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;
|
||||
|
||||
public MirrorEntryInfoBase(PacketSender packetSender, MirrorConfigCategory category, String entryName) {
|
||||
super(packetSender);
|
||||
this.category = category;
|
||||
this.entryName = entryName;
|
||||
}
|
||||
|
||||
protected void writePath(PacketByteBuf buf) {
|
||||
category.writeCategoryPath(buf);
|
||||
buf.writeString(entryName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return entryName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFromJson(JsonReader reader) throws IOException, IllegalAccessException {
|
||||
throw new UnsupportedEncodingException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(JsonWriter writer, String translationPrefix) throws IOException, IllegalAccessException {
|
||||
throw new UnsupportedEncodingException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fix() {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writePath(buf);
|
||||
sendRequest("fixEntry", buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IllegalAccessException {
|
||||
PacketByteBuf buf = PacketByteBufs.create();
|
||||
writePath(buf);
|
||||
sendRequest("resetEntry", buf);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.rci.entry;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.type.Type;
|
||||
import io.gitlab.jfronny.libjf.config.impl.network.rci.MirrorConfigCategory;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
public class MirrorEntryInfoUnsupported<T> extends MirrorEntryInfoBase<T> {
|
||||
public MirrorEntryInfoUnsupported(PacketSender packetSender, MirrorConfigCategory category, String entryName) {
|
||||
super(packetSender, category, entryName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getDefault() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getValueType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxValue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
package io.gitlab.jfronny.libjf.config.impl.network.server;
|
||||
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.*;
|
||||
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.rci.entry.Datatype;
|
||||
import io.gitlab.jfronny.libjf.config.impl.network.rci.entry.MirrorEntryInfo;
|
||||
import net.fabricmc.fabric.api.networking.v1.*;
|
||||
import net.minecraft.network.PacketByteBuf;
|
||||
import net.minecraft.network.packet.Packet;
|
||||
import net.minecraft.server.network.ServerPlayerConfigurationTask;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
import java.util.*;
|
||||
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);
|
||||
});
|
||||
ServerPlayNetworking.registerGlobalReceiver(RequestRouter.RESPONSE_ID, (server, player, handler, buf, responseSender) -> {
|
||||
if (authenticate(player)) RequestRouter.acceptResponse(buf, responseSender);
|
||||
else RequestRouter.deny(buf, responseSender);
|
||||
});
|
||||
ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.PACKET_TYPE, (packet, networkHandler, responseSender) -> {
|
||||
networkHandler.completeTask(JfConfigNetworkConfigurationTask.KEY);
|
||||
});
|
||||
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
|
||||
if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) {
|
||||
handler.addTask(new JfConfigNetworkConfigurationTask());
|
||||
}
|
||||
});
|
||||
registerRPC();
|
||||
}
|
||||
|
||||
public record JfConfigNetworkConfigurationTask() implements ServerPlayerConfigurationTask {
|
||||
public static final Key KEY = new Key(ConfigurationPacket.ID.toString());
|
||||
|
||||
@Override
|
||||
public void sendPacket(Consumer<Packet<?>> sender) {
|
||||
sender.accept(ServerConfigurationNetworking.createS2CPacket(new ConfigurationPacket(RequestRouter.PROTOCOL_VERSION)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key getKey() {
|
||||
return KEY;
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerRPC() {
|
||||
ConfigHolder ch = ConfigHolder.getInstance();
|
||||
RequestRouter.registerHandler("getRegistered", (buf, followupSender) -> {
|
||||
PacketByteBuf resp = PacketByteBufs.create();
|
||||
resp.writeCollection(ch.getRegistered().keySet(), PacketByteBuf::writeString);
|
||||
return resp;
|
||||
});
|
||||
RequestRouter.registerHandler("isRegistered", (buf, followupSender) -> {
|
||||
PacketByteBuf resp = PacketByteBufs.create();
|
||||
resp.writeBoolean(ch.isRegistered(buf.readString()));
|
||||
return resp;
|
||||
});
|
||||
RequestRouter.registerHandler("migrateFiles", (buf, followupSender) -> {
|
||||
ch.migrateFiles(buf.readString());
|
||||
return null;
|
||||
});
|
||||
RequestRouter.registerHandler("load", (buf, followupSender) -> {
|
||||
readInstance(ch, buf).load();
|
||||
return null;
|
||||
});
|
||||
RequestRouter.registerHandler("write", (buf, followupSender) -> {
|
||||
readInstance(ch, buf).write();
|
||||
return null;
|
||||
});
|
||||
RequestRouter.registerHandler("getPresets", (buf, followupSender) -> {
|
||||
PacketByteBuf resp = PacketByteBufs.create();
|
||||
resp.writeCollection(readCategory(ch, buf).getPresets().keySet(), PacketByteBuf::writeString);
|
||||
return resp;
|
||||
});
|
||||
RequestRouter.registerHandler("runPreset", (buf, followupSender) -> {
|
||||
Objects.requireNonNull(readCategory(ch, buf).getPresets().get(buf.readString())).run();
|
||||
return null;
|
||||
});
|
||||
RequestRouter.registerHandler("getReferencedConfigs", (buf, followupSender) -> {
|
||||
PacketByteBuf resp = PacketByteBufs.create();
|
||||
resp.writeCollection(readCategory(ch, buf).getReferencedConfigs(), (b, ci) -> {
|
||||
b.writeBoolean(Objects.equals(ch.get(ci.getId()), ci));
|
||||
b.writeString(ci.getId());
|
||||
});
|
||||
return resp;
|
||||
});
|
||||
RequestRouter.registerHandler("getCategories", (buf, followupSender) -> {
|
||||
PacketByteBuf resp = PacketByteBufs.create();
|
||||
resp.writeCollection(readCategory(ch, buf).getCategories().keySet(), PacketByteBuf::writeString);
|
||||
return resp;
|
||||
});
|
||||
RequestRouter.registerHandler("getEntries", (buf, followupSender) -> {
|
||||
PacketByteBuf resp = PacketByteBufs.create();
|
||||
resp.writeCollection(readCategory(ch, buf).getEntries(), (b, s) -> {
|
||||
b.writeString(s.getName());
|
||||
if (s.supportsRepresentation()) {
|
||||
Type type = s.getValueType();
|
||||
boolean foundType = true;
|
||||
if (type.isInt()) {
|
||||
b.writeInt(Datatype.INT.ordinal());
|
||||
} else if (type.isLong()) {
|
||||
b.writeInt(Datatype.LONG.ordinal());
|
||||
} else if (type.isFloat()) {
|
||||
b.writeInt(Datatype.FLOAT.ordinal());
|
||||
} else if (type.isDouble()) {
|
||||
b.writeInt(Datatype.DOUBLE.ordinal());
|
||||
} else if (type.isString()) {
|
||||
b.writeInt(Datatype.STRING.ordinal());
|
||||
} else if (type.isBool()) {
|
||||
b.writeInt(Datatype.BOOL.ordinal());
|
||||
} else if (type.isEnum()) {
|
||||
b.writeInt(Datatype.ENUM.ordinal());
|
||||
b.writeCollection(Arrays.stream(type.asEnum().options()).map(Object::toString).toList(), PacketByteBuf::writeString);
|
||||
} else {
|
||||
foundType = false;
|
||||
b.writeInt(-1);
|
||||
}
|
||||
if (foundType) {
|
||||
MirrorEntryInfo.write(b, type, s.getDefault());
|
||||
b.writeInt(s.getWidth());
|
||||
b.writeDouble(s.getMinValue());
|
||||
b.writeDouble(s.getMaxValue());
|
||||
}
|
||||
} else {
|
||||
b.writeInt(-1);
|
||||
}
|
||||
});
|
||||
return resp;
|
||||
});
|
||||
RequestRouter.registerHandler("fixEntry", (buf, followupSender) -> {
|
||||
readEntry(ch, buf).fix();
|
||||
return null;
|
||||
});
|
||||
RequestRouter.registerHandler("resetEntry", (buf, followupSender) -> {
|
||||
readEntry(ch, buf).reset();
|
||||
return null;
|
||||
});
|
||||
RequestRouter.registerHandler("getEntryValue", (buf, followupSender) -> {
|
||||
PacketByteBuf resp = PacketByteBufs.create();
|
||||
EntryInfo entryInfo = readEntry(ch, buf);
|
||||
MirrorEntryInfo.write(resp, entryInfo.getValueType(), entryInfo.getValue());
|
||||
return resp;
|
||||
});
|
||||
RequestRouter.registerHandler("setEntryValue", (buf, followupSender) -> {
|
||||
EntryInfo entryInfo = readEntry(ch, buf);
|
||||
entryInfo.setValue(MirrorEntryInfo.read(buf, entryInfo.getValueType()));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static ConfigInstance readInstance(ConfigHolder ch, PacketByteBuf buf) {
|
||||
List<List<String>> parentPaths = buf.readList(s -> s.readList(PacketByteBuf::readString));
|
||||
String id = buf.readString();
|
||||
if (parentPaths.isEmpty()) return Objects.requireNonNull(ch.get(id));
|
||||
parentPaths = new LinkedList<>(parentPaths);
|
||||
ConfigCategory ci = null;
|
||||
for (List<String> parentPath : parentPaths) {
|
||||
ci = ci == null
|
||||
? ch.get(parentPath.get(0))
|
||||
: getReferencedConfig(ci, parentPath.get(0));
|
||||
for (String segment : parentPath.subList(1, parentPath.size() - 1)) {
|
||||
ci = Objects.requireNonNull(ci.getCategories().get(segment));
|
||||
}
|
||||
}
|
||||
return getReferencedConfig(Objects.requireNonNull(ci), id);
|
||||
}
|
||||
|
||||
private static ConfigInstance getReferencedConfig(ConfigCategory category, String id) {
|
||||
return category.getReferencedConfigs()
|
||||
.stream()
|
||||
.filter(s -> s.getId().equals(id))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
private static ConfigCategory readCategory(ConfigHolder ch, PacketByteBuf buf) {
|
||||
ConfigCategory result = readInstance(ch, buf);
|
||||
for (String s : buf.readList(PacketByteBuf::readString)) {
|
||||
result = Objects.requireNonNull(result.getCategories().get(s));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static EntryInfo readEntry(ConfigHolder ch, PacketByteBuf buf) {
|
||||
ConfigCategory cat = readCategory(ch, buf);
|
||||
String name = buf.readString();
|
||||
return cat.getEntries().stream().filter(s -> s.getName().equals(name)).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
private static boolean authenticate(ServerPlayerEntity player) {
|
||||
return player.hasPermissionLevel(2);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-config-network-v0",
|
||||
"name": "LibJF Config: Network",
|
||||
"version": "${version}",
|
||||
"authors": ["JFronny"],
|
||||
"contact": {
|
||||
"email": "projects.contact@frohnmeyer-wds.de",
|
||||
"homepage": "https://jfronny.gitlab.io",
|
||||
"issues": "https://git.frohnmeyer-wds.de/JfMods/LibJF/issues",
|
||||
"sources": "https://git.frohnmeyer-wds.de/JfMods/LibJF"
|
||||
},
|
||||
"license": "MIT",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"server": ["io.gitlab.jfronny.libjf.config.impl.network.server.JfConfigNetworkServer::initialize"],
|
||||
"client": ["io.gitlab.jfronny.libjf.config.impl.network.client.JfConfigNetworkClient::initialize"]
|
||||
},
|
||||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*",
|
||||
"fabric-networking-api-v1": "*",
|
||||
"fabric-command-api-v2": "*",
|
||||
"libjf-base": ">=${version}",
|
||||
"libjf-config-core-v2": ">=${version}"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
"badges": ["library"],
|
||||
"parent": "libjf"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,8 +22,8 @@ public class TinyConfigScreenFactory implements ConfigScreenFactory<Screen, Tiny
|
|||
&& config.getReferencedConfigs().isEmpty()
|
||||
&& config.getCategories().isEmpty()) {
|
||||
EntryInfo entry = config.getEntries().get(0);
|
||||
Type type = entry.getValueType();
|
||||
if (!type.isInt() && !type.isLong() && !type.isFloat() && !type.isDouble() && !type.isString() && !type.isBool() && !type.isEnum()) {
|
||||
Type type = entry.supportsRepresentation() ? entry.getValueType() : null;
|
||||
if (type != null && !type.isInt() && !type.isLong() && !type.isFloat() && !type.isDouble() && !type.isString() && !type.isBool() && !type.isEnum()) {
|
||||
final String jsonified;
|
||||
try {
|
||||
jsonified = GsonHolders.CONFIG.getGson().toJson(entry.getValue());
|
||||
|
|
|
@ -25,7 +25,8 @@ public class TinyConfigTab implements Tab {
|
|||
|
||||
public TinyConfigTab(TinyConfigScreen screen, ConfigCategory config, TextRenderer textRenderer, boolean isRoot) {
|
||||
this.config = config;
|
||||
List<WidgetState<?>> widgets = EntryInfoWidgetBuilder.buildWidgets(config, screen.widgets);
|
||||
List<String> erroredEntries = new LinkedList<>();
|
||||
List<WidgetState<?>> widgets = EntryInfoWidgetBuilder.buildWidgets(config, screen.widgets, erroredEntries);
|
||||
|
||||
config.fix();
|
||||
for (WidgetState<?> widget : widgets) {
|
||||
|
@ -40,6 +41,10 @@ public class TinyConfigTab implements Tab {
|
|||
this.list.addText(Text.translatable(tooltipPath));
|
||||
}
|
||||
|
||||
for (String erroredEntry : erroredEntries) {
|
||||
this.list.addText(Text.translatable(ConfigCore.MOD_ID + ".errored_entry", erroredEntry));
|
||||
}
|
||||
|
||||
if (!isRoot && !config.getPresets().isEmpty()) {
|
||||
this.list.addReference(Text.translatable(ConfigCore.MOD_ID + ".presets"),
|
||||
() -> new PresetsScreen(screen, config, screen::afterSelectPreset));
|
||||
|
|
|
@ -30,12 +30,16 @@ public class EntryInfoWidgetBuilder {
|
|||
private static final Pattern INTEGER_ONLY = Pattern.compile("(-?\\d*)");
|
||||
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)");
|
||||
|
||||
public static List<WidgetState<?>> buildWidgets(ConfigCategory config, List<WidgetState<?>> knownStates) {
|
||||
public static List<WidgetState<?>> buildWidgets(ConfigCategory config, List<WidgetState<?>> knownStates, List<String> erroredEntries) {
|
||||
List<WidgetState<?>> knownStates2 = new LinkedList<>();
|
||||
for (EntryInfo<?> info : config.getEntries()) {
|
||||
WidgetState<?> state = initEntry(config, info, knownStates);
|
||||
knownStates.add(state);
|
||||
knownStates2.add(state);
|
||||
if (info.supportsRepresentation()) {
|
||||
WidgetState<?> state = initEntry(config, info, knownStates);
|
||||
knownStates.add(state);
|
||||
knownStates2.add(state);
|
||||
} else {
|
||||
erroredEntries.add(info.getName());
|
||||
}
|
||||
}
|
||||
return knownStates2;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.gitlab.jfronny.libjf.config.api.v2.dsl.DSL;
|
|||
public class TestConfig implements JfCustomConfig {
|
||||
private int value1 = 0;
|
||||
private double doubleValue = 0.3;
|
||||
private double doubleValue2 = 0.3;
|
||||
private String value2 = "";
|
||||
private boolean value3 = false;
|
||||
private int value4 = 0;
|
||||
|
@ -18,6 +19,7 @@ public class TestConfig implements JfCustomConfig {
|
|||
.category("ca1", builder1 -> builder1
|
||||
.value("value1", value1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, () -> value1, v -> value1 = v)
|
||||
.value("doubleValue", doubleValue, -0.74, 1.6, () -> doubleValue, v -> doubleValue = v)
|
||||
.category("nestedCa", builder2 -> builder2.value("doubleValue", doubleValue2, 12, 47, () -> doubleValue2, v -> doubleValue2 = v))
|
||||
).category("ca2", builder1 -> builder1
|
||||
.value("value2", value2, () -> value2, v -> value2 = v)
|
||||
).category("ca3", builder1 -> builder1
|
||||
|
|
|
@ -16,6 +16,7 @@ include("libjf-base")
|
|||
|
||||
include("libjf-config-core-v2")
|
||||
include("libjf-config-commands")
|
||||
include("libjf-config-network-v0")
|
||||
include("libjf-config-ui-tiny")
|
||||
include("libjf-config-compiler-plugin-v2")
|
||||
|
||||
|
|
Loading…
Reference in New Issue