[config] Remove internal APIs from interfaces to allow reusing the related systems elsewhere

This commit is contained in:
Johannes Frohnmeyer 2022-07-01 19:16:40 +02:00
parent 06bd6a1961
commit f53e9b74b6
Signed by: Johannes
GPG Key ID: E76429612C2929F4
11 changed files with 50 additions and 64 deletions

View File

@ -47,7 +47,7 @@ Enum keys are translated as follows: `<mod id>.jfconfig.enum.<enum class name>.<
## Categories
Categories can be added by creating public static subclasses in your config class and annotating them with @Category.
Entries will be read as before, however translations for fields will use `jfconfig.<category>.<field name>` instead of `jfconfig.<field name>`
Entries will be read as before, however the translation prefix will be `jfconfig.<category>.` instead of `jfconfig.`
## Presets
libjf-config-v0 provides a preset system to automatically fill in certain values based on a function.

View File

@ -15,6 +15,7 @@ public class LibJf {
static {
GsonHolder.modifyBuilder((final GsonBuilder builder) -> {
builder.setOmitQuotes();
for (GsonAdapter adapter : FabricLoader.getInstance().getEntrypoints(MOD_ID + ":gson_adapter", GsonAdapter.class)) {
adapter.apply(builder);
}

View File

@ -1,10 +1,5 @@
package io.gitlab.jfronny.libjf.config.api;
import io.gitlab.jfronny.gson.JsonObject;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import org.jetbrains.annotations.ApiStatus;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@ -17,15 +12,20 @@ public interface ConfigInstance {
}
void load();
void write();
String getModId();
String getId();
String getCategoryPath();
default String getTranslationPrefix() {
return getId() + ".jfconfig." + getCategoryPath();
}
List<EntryInfo<?>> getEntries();
Map<String, Runnable> getPresets();
List<String> getReferencedConfigs();
default List<ConfigInstance> getReferencedConfigs() {
return List.of();
}
Map<String, ConfigInstance> getCategories();
@ApiStatus.Internal void verify();
@ApiStatus.Internal boolean matchesConfigClass(Class<?> candidate);
@ApiStatus.Internal void loadFrom(JsonObject source);
@ApiStatus.Internal void writeTo(JsonWriter writer) throws IOException;
@ApiStatus.Internal String getCategoryPath();
default void fix() {
for (EntryInfo<?> entry : getEntries()) {
entry.fix();
}
}
}

View File

@ -2,26 +2,10 @@ package io.gitlab.jfronny.libjf.config.api;
import io.gitlab.jfronny.gson.JsonElement;
import io.gitlab.jfronny.gson.stream.JsonWriter;
import io.gitlab.jfronny.libjf.config.impl.EntryInfoImpl;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.lang.reflect.Field;
public interface EntryInfo<T> {
static <T> @Nullable EntryInfo<T> fromField(Field field) {
if (!field.isAnnotationPresent(Entry.class)) {
return null;
}
EntryInfoImpl<T> info = new EntryInfoImpl<>();
info.field = field;
info.entry = field.getAnnotation(Entry.class);
try {
info.defaultValue = (T) field.get(null);
} catch (IllegalAccessException ignored) {}
return info;
}
/**
* @return Get the default value of this entry
*/

View File

@ -24,7 +24,7 @@ public class ConfigHolderImpl implements ConfigHolder {
@Override
public void register(String modId, Class<?> config) {
if (isRegistered(modId)) {
if (get(modId).matchesConfigClass(config)) {
if (get(modId) instanceof ConfigInstanceAbstract instance && instance.matchesConfigClass(config)) {
SafeLog.warn("Attempted to set config of " + modId + " twice, skipping");
return;
}
@ -61,7 +61,7 @@ public class ConfigHolderImpl implements ConfigHolder {
@Override
public ConfigInstance get(Class<?> configClass) {
for (ConfigInstance value : configs.values()) {
if (value.matchesConfigClass(configClass))
if (value instanceof ConfigInstanceAbstract instance && instance.matchesConfigClass(configClass))
return value;
}
return null;
@ -80,7 +80,7 @@ public class ConfigHolderImpl implements ConfigHolder {
@Override
public boolean isRegistered(Class<?> config) {
for (ConfigInstance value : configs.values()) {
if (value.matchesConfigClass(config))
if (value instanceof ConfigInstanceAbstract instance && instance.matchesConfigClass(config))
return true;
}
return false;

View File

@ -29,8 +29,8 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
this.configClass = configClass;
this.referencedConfigs = List.copyOf(meta.referencedConfigs);
for (Field field : configClass.getFields()) {
EntryInfo<?> newInfo = EntryInfo.fromField(field);
if (newInfo != null) entries.add(newInfo);
if (field.isAnnotationPresent(Entry.class))
entries.add(new EntryInfoImpl<>(field));
}
presets.put(CONFIG_PRESET_DEFAULT, () -> {
for (EntryInfo<?> entry : entries) {
@ -62,11 +62,6 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
});
}
}
verifiers.add(() -> {
for (EntryInfo<?> entry : entries) {
entry.fix();
}
});
for (Class<?> categoryClass : configClass.getClasses()) {
if (categoryClass.isAnnotationPresent(Category.class)) {
@ -88,7 +83,6 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
return Character.toLowerCase(source.charAt(0)) + source.substring(1);
}
@Override
public void loadFrom(JsonObject source) {
for (EntryInfo<?> entry : entries) {
if (source.has(entry.getName())) {
@ -100,17 +94,16 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
} else SafeLog.error("Config does not contain entry for " + entry.getName());
}
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
if (source.has(entry.getKey())) {
if (entry.getValue() instanceof ConfigInstanceAbstract instance && source.has(entry.getKey())) {
JsonElement el = source.get(entry.getKey());
if (el.isJsonObject()) entry.getValue().loadFrom(el.getAsJsonObject());
if (el.isJsonObject()) instance.loadFrom(el.getAsJsonObject());
else SafeLog.error("Config category is not a JSON object, skipping");
} else SafeLog.error("Config does not contain entry for subcategory " + entry.getKey());
}
}
@Override
public void writeTo(JsonWriter writer) throws IOException {
verify();
fix();
writer.beginObject();
String val;
for (EntryInfo<?> entry : entries) {
@ -121,26 +114,27 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
}
}
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
if (!(entry.getValue() instanceof ConfigInstanceAbstract instance)) continue;
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(modId + ".jfconfig." + categoryPath + entry.getKey() + ".title")) != null)
writer.comment(val);
writer.name(entry.getKey());
entry.getValue().writeTo(writer);
instance.writeTo(writer);
}
writer.endObject();
}
@Override
public void verify() {
public void fix() {
for (Runnable verifier : verifiers) verifier.run();
for (EntryInfo<?> entry : entries) entry.fix();
}
@Override
public boolean matchesConfigClass(Class<?> candidate) {
return candidate != null && candidate.isAssignableFrom(configClass);
}
@Override
public String getModId() {
public String getId() {
return modId;
}
@ -160,8 +154,13 @@ public abstract class ConfigInstanceAbstract implements ConfigInstance {
}
@Override
public List<String> getReferencedConfigs() {
return referencedConfigs;
public List<ConfigInstance> getReferencedConfigs() {
List<ConfigInstance> result = new LinkedList<>();
for (String referencedConfig : referencedConfigs) {
ConfigInstance ci = ConfigHolder.getInstance().get(referencedConfig);
if (ci != null) result.add(ci);
}
return result;
}
@Override

View File

@ -18,7 +18,12 @@ public class EntryInfoImpl<T> implements EntryInfo<T> {
public T defaultValue;
public Entry entry;
public EntryInfoImpl() {
public EntryInfoImpl(Field field) {
this.field = field;
this.entry = field.getAnnotation(Entry.class);
try {
this.defaultValue = (T) field.get(null);
} catch (IllegalAccessException ignored) {}
}
@Override

View File

@ -3,14 +3,13 @@ package io.gitlab.jfronny.libjf.config.impl.client;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.impl.client.gui.EntryInfoWidgetBuilder;
import net.fabricmc.api.ClientModInitializer;
public class JfConfigClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
for (ConfigInstance config : ConfigHolder.getInstance().getRegistered().values()) {
LibJf.LOGGER.info("Registering config UI for " + config.getModId());
LibJf.LOGGER.info("Registering config UI for " + config.getId());
}
}
}

View File

@ -53,7 +53,7 @@ public class EntryInfoWidgetBuilder {
factory = toggle(info, state, value -> {
int index = values.indexOf(value) + 1;
return values.get(index >= values.size() ? 0 : index);
}, value -> Text.translatable(config.getModId() + ".jfconfig.enum." + type.getSimpleName() + "." + state.cachedValue));
}, value -> Text.translatable(config.getTranslationPrefix() + "enum." + type.getSimpleName() + "." + state.cachedValue));
} else {
LibJf.LOGGER.error("Invalid entry type in " + info.getName() + ": " + type.getName());
factory = ((screenWidth, textRenderer, done) -> new WidgetFactory.Widget(() -> {}, new ButtonWidget(-10, 0, 0, 0, Text.of(""), null)));

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.libjf.config.impl.client.gui;
import io.gitlab.jfronny.commons.throwable.Try;
import io.gitlab.jfronny.libjf.config.api.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.ConfigInstance;
import io.gitlab.jfronny.libjf.config.api.WidgetFactory;
import io.gitlab.jfronny.libjf.config.impl.client.gui.presets.PresetsScreen;
@ -21,11 +20,11 @@ import java.util.*;
@Environment(EnvType.CLIENT)
public class TinyConfigScreen extends Screen {
public TinyConfigScreen(ConfigInstance config, Screen parent) {
super(Text.translatable(config.getModId() + ".jfconfig." + config.getCategoryPath() + "title"));
super(Text.translatable(config.getTranslationPrefix() + "title"));
this.parent = parent;
this.config = config;
this.widgets = EntryInfoWidgetBuilder.buildWidgets(config);
this.translationPrefix = config.getModId() + ".jfconfig." + config.getCategoryPath();
this.translationPrefix = config.getTranslationPrefix();
}
private final String translationPrefix;
private final Screen parent;
@ -37,7 +36,7 @@ public class TinyConfigScreen extends Screen {
protected void init() {
super.init();
config.verify();
config.fix();
this.addDrawableChild(new ButtonWidget(4, 6, 80, 20, Text.translatable("libjf-config-v0.presets"), button -> {
MinecraftClient.getInstance().setScreen(new PresetsScreen(this, config));
@ -60,7 +59,7 @@ public class TinyConfigScreen extends Screen {
this.addSelectableChild(this.list);
for (Map.Entry<String, ConfigInstance> entry : config.getCategories().entrySet()) {
this.list.addReference(width / 2,
Text.translatable(entry.getValue().getModId() + ".jfconfig." + entry.getValue().getCategoryPath() + "title"),
Text.translatable(entry.getValue().getTranslationPrefix() + "title"),
() -> new TinyConfigScreen(entry.getValue(), this));
}
for (WidgetState<?> info : widgets) {
@ -76,11 +75,10 @@ public class TinyConfigScreen extends Screen {
return visible;
}, name);
}
for (String referencedConfig : config.getReferencedConfigs()) {
ConfigInstance ci = ConfigHolder.getInstance().get(referencedConfig);
for (ConfigInstance ci : config.getReferencedConfigs()) {
if (ci != null) {
this.list.addReference(width / 2,
Text.translatable("libjf-config-v0.see-also", Text.translatable(ci.getModId() + ".jfconfig." + ci.getCategoryPath() + "title")),
Text.translatable("libjf-config-v0.see-also", Text.translatable(ci.getTranslationPrefix() + "title")),
() -> new TinyConfigScreen(ci, this));
}
}

View File

@ -34,7 +34,7 @@ public class PresetsScreen extends Screen {
button -> {
LibJf.LOGGER.info("Preset selected: " + entry.getKey());
entry.getValue().run();
config.verify();
config.fix();
MinecraftClient.getInstance().setScreen(parent);
}));
}