Add data driven item stack tag filtering when exposing them to VM via serialization. Closes #5.

This commit is contained in:
Florian Nücke
2021-02-09 18:12:00 +01:00
parent 1996a8addc
commit 328bbbbeb4
13 changed files with 224 additions and 38 deletions

View File

@@ -81,7 +81,6 @@ public final class Callbacks {
public static boolean hasMethods(final Object object) {
if (object instanceof Class<?>) {
return !getMethods((Class<?>) object).isEmpty();
} else {
return !getMethods(object.getClass()).isEmpty();
}

View File

@@ -1,38 +1,33 @@
package li.cil.oc2.common;
import li.cil.oc2.common.bus.device.data.FileSystems;
import li.cil.oc2.common.bus.device.rpc.RPCItemStackTagFilters;
import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters;
import li.cil.oc2.common.capabilities.Capabilities;
import li.cil.oc2.common.integration.IMC;
import li.cil.oc2.common.network.Network;
import li.cil.oc2.common.serialization.BlobStorage;
import li.cil.oc2.common.serialization.serializers.DirectionJsonSerializer;
import li.cil.oc2.common.serialization.serializers.ItemStackJsonSerializer;
import li.cil.oc2.common.util.ServerScheduler;
import li.cil.oc2.common.vm.Allocator;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.server.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
public final class CommonSetup {
@SubscribeEvent
public static void handleSetupEvent(final FMLCommonSetupEvent event) {
Capabilities.initialize();
FileSystems.initialize();
IMC.initialize();
Network.initialize();
RPCItemStackTagFilters.initialize();
RPCMethodParameterTypeAdapters.initialize();
ServerScheduler.initialize();
Network.setup();
FMLJavaModLoadingContext.get().getModEventBus().addListener(IMC::handleIMCMessages);
MinecraftForge.EVENT_BUS.addListener(CommonSetup::handleServerAboutToStart);
MinecraftForge.EVENT_BUS.addListener(CommonSetup::handleServerStopped);
MinecraftForge.EVENT_BUS.addListener(FileSystems::handleAddReloadListenerEvent);
ServerScheduler.register();
addBuiltinRPCMethodParameterTypeAdapters();
}
///////////////////////////////////////////////////////////////////
@@ -46,9 +41,4 @@ public final class CommonSetup {
Allocator.resetAndCheckLeaks();
FileSystems.reset();
}
private static void addBuiltinRPCMethodParameterTypeAdapters() {
RPCMethodParameterTypeAdapters.addTypeAdapter(ItemStack.class, new ItemStackJsonSerializer());
RPCMethodParameterTypeAdapters.addTypeAdapter(Direction.class, new DirectionJsonSerializer());
}
}

View File

@@ -2,7 +2,6 @@ package li.cil.oc2.common.bus.device.data;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import li.cil.oc2.api.API;
import li.cil.oc2.common.vm.fs.LayeredFileSystem;
import li.cil.oc2.common.vm.fs.ResourceFileSystem;
import li.cil.sedna.fs.FileSystem;
@@ -11,6 +10,7 @@ import net.minecraft.resources.IFutureReloadListener;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.AddReloadListenerEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -26,6 +26,10 @@ public final class FileSystems {
///////////////////////////////////////////////////////////////////
public static void initialize() {
MinecraftForge.EVENT_BUS.addListener(FileSystems::handleAddReloadListenerEvent);
}
public static FileSystem getLayeredFileSystem() {
return LAYERED_FILE_SYSTEM;
}
@@ -34,12 +38,12 @@ public final class FileSystems {
LAYERED_FILE_SYSTEM.clear();
}
public static void handleAddReloadListenerEvent(final AddReloadListenerEvent event) {
event.addListener(FileSystemsReloadListener.INSTANCE);
}
///////////////////////////////////////////////////////////////////
private static void handleAddReloadListenerEvent(final AddReloadListenerEvent event) {
event.addListener(ReloadListener.INSTANCE);
}
private static void reload(final IResourceManager resourceManager) {
reset();
@@ -83,13 +87,8 @@ public final class FileSystems {
///////////////////////////////////////////////////////////////////
private static final class FileSystemsReloadListener implements IFutureReloadListener {
public static final FileSystemsReloadListener INSTANCE = new FileSystemsReloadListener();
@Override
public String getSimpleName() {
return API.MOD_ID + ":file_system";
}
private static final class ReloadListener implements IFutureReloadListener {
public static final ReloadListener INSTANCE = new ReloadListener();
@Override
public CompletableFuture<Void> reload(final IFutureReloadListener.IStage stage, final IResourceManager resourceManager, final IProfiler preparationsProfiler, final IProfiler reloadProfiler, final Executor backgroundExecutor, final Executor gameExecutor) {

View File

@@ -0,0 +1,83 @@
package li.cil.oc2.common.bus.device.rpc;
import li.cil.oc2.common.util.NBTTagIds;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StringUtils;
import javax.annotation.Nullable;
import java.util.Objects;
public final class RPCItemStackTagFilter {
public ResourceLocation item;
public String[] tags;
private String[][] paths; // Cache of resolved paths specified in tags.
///////////////////////////////////////////////////////////////////
@Nullable
public CompoundNBT apply(final ItemStack stack, final CompoundNBT tag) {
if (stack.isEmpty() || tags == null) {
return null;
}
if (item != null && !Objects.equals(stack.getItem().getRegistryName(), item)) {
return null;
}
validatePaths();
final CompoundNBT filtered = new CompoundNBT();
for (final String[] path : paths) {
final CompoundNBT filteredByPath = filterPath(path, tag);
if (filteredByPath != null) {
filtered.merge(filteredByPath);
}
}
return filtered;
}
///////////////////////////////////////////////////////////////////
@Nullable
private CompoundNBT filterPath(final String[] path, final CompoundNBT source) {
if (path.length == 0) {
return null;
}
final CompoundNBT result = new CompoundNBT();
CompoundNBT currentSource = source;
CompoundNBT currentTarget = result;
for (int j = 0; j < path.length - 1; j++) {
final String segment = path[j];
if (currentSource.contains(segment, NBTTagIds.TAG_COMPOUND)) {
currentSource = currentSource.getCompound(segment);
currentTarget.put(segment, new CompoundNBT());
currentTarget = currentTarget.getCompound(segment);
} else {
return null; // Path mismatch, inner element is not a compound tag.
}
}
if (!currentSource.contains(path[path.length - 1])) {
return null; // Cannot find tag at path.
}
currentTarget.put(path[path.length - 1], currentSource.get(path[path.length - 1]));
return result;
}
private void validatePaths() {
paths = new String[tags.length][];
for (int i = 0; i < tags.length; i++) {
if (!StringUtils.isNullOrEmpty(tags[i])) {
paths[i] = tags[i].split("\\.");
}
}
}
}

View File

@@ -0,0 +1,79 @@
package li.cil.oc2.common.bus.device.rpc;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import net.minecraft.client.resources.JsonReloadListener;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.profiler.IProfiler;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.AddReloadListenerEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Map;
public final class RPCItemStackTagFilters {
private static final Logger LOGGER = LogManager.getLogger();
private static final ArrayList<RPCItemStackTagFilter> FILTERS = new ArrayList<>();
///////////////////////////////////////////////////////////////////
public static void initialize() {
MinecraftForge.EVENT_BUS.addListener(RPCItemStackTagFilters::handleAddReloadListenerEvent);
}
@Nullable
public static CompoundNBT getFilteredTag(final ItemStack stack, final CompoundNBT tag) {
final CompoundNBT result = new CompoundNBT();
for (final RPCItemStackTagFilter filter : FILTERS) {
final CompoundNBT filtered = filter.apply(stack, tag);
if (filtered != null) {
result.merge(filtered);
}
}
return result;
}
///////////////////////////////////////////////////////////////////
private static void handleAddReloadListenerEvent(final AddReloadListenerEvent event) {
event.addListener(ReloadListener.INSTANCE);
}
///////////////////////////////////////////////////////////////////
private static final class ReloadListener extends JsonReloadListener {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(ResourceLocation.class, new ResourceLocation.Serializer())
.create();
public static final ReloadListener INSTANCE = new ReloadListener();
public ReloadListener() {
super(GSON, "item_tag_filters");
}
@Override
protected void apply(final Map<ResourceLocation, JsonElement> objects, final IResourceManager resourceManager, final IProfiler profiler) {
FILTERS.clear();
objects.forEach((location, element) -> {
try {
final RPCItemStackTagFilter filter = GSON.fromJson(element, RPCItemStackTagFilter.class);
if (filter != null) {
FILTERS.add(filter);
}
} catch (final Exception e) {
LOGGER.error("Failed loading item tag filter [{}].", location, e);
}
});
}
}
}

View File

@@ -2,6 +2,10 @@ package li.cil.oc2.common.bus.device.rpc;
import com.google.gson.GsonBuilder;
import li.cil.oc2.api.imc.RPCMethodParameterTypeAdapter;
import li.cil.oc2.common.serialization.serializers.DirectionJsonSerializer;
import li.cil.oc2.common.serialization.serializers.ItemStackJsonSerializer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import java.util.ArrayList;
@@ -10,6 +14,11 @@ public final class RPCMethodParameterTypeAdapters {
///////////////////////////////////////////////////////////////////
public static void initialize() {
addTypeAdapter(ItemStack.class, new ItemStackJsonSerializer());
addTypeAdapter(Direction.class, new DirectionJsonSerializer());
}
public static void addTypeAdapter(final Class<?> type, final Object typeAdapter) {
addTypeAdapter(new RPCMethodParameterTypeAdapter(type, typeAdapter));
}

View File

@@ -6,6 +6,7 @@ import li.cil.oc2.common.bus.device.rpc.RPCMethodParameterTypeAdapters;
import net.minecraft.util.Util;
import net.minecraftforge.fml.InterModComms;
import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -24,7 +25,15 @@ public final class IMC {
return map;
});
public static void handleIMCMessages(final InterModProcessEvent event) {
///////////////////////////////////////////////////////////////////
public static void initialize() {
FMLJavaModLoadingContext.get().getModEventBus().addListener(IMC::handleIMCMessages);
}
///////////////////////////////////////////////////////////////////
private static void handleIMCMessages(final InterModProcessEvent event) {
event.getIMCStream().forEach(message -> {
final Consumer<InterModComms.IMCMessage> method = METHODS.get(message.getMethod());
if (method != null) {

View File

@@ -26,7 +26,7 @@ public final class Network {
///////////////////////////////////////////////////////////////////
public static void setup() {
public static void initialize() {
INSTANCE.messageBuilder(ComputerTerminalOutputMessage.class, getNextPacketId(), NetworkDirection.PLAY_TO_CLIENT)
.encoder(ComputerTerminalOutputMessage::toBytes)
.decoder(ComputerTerminalOutputMessage::new)

View File

@@ -4,6 +4,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import li.cil.oc2.common.bus.device.rpc.RPCItemStackTagFilters;
import li.cil.oc2.common.serialization.NBTToJsonConverter;
import net.minecraft.item.ItemStack;
@@ -16,10 +17,6 @@ public final class ItemStackJsonSerializer implements JsonSerializer<ItemStack>
return JsonNull.INSTANCE;
}
final JsonElement json = NBTToJsonConverter.convert(src.serializeNBT());
// TODO Postprocessing, filter out tags that should not be visible to the VM.
return json;
return NBTToJsonConverter.convert(RPCItemStackTagFilters.getFilteredTag(src, src.serializeNBT()));
}
}

View File

@@ -21,7 +21,7 @@ public final class ServerScheduler {
///////////////////////////////////////////////////////////////////
public static void register() {
public static void initialize() {
MinecraftForge.EVENT_BUS.register(EventHandler.class);
}

View File

@@ -0,0 +1,9 @@
{
"tags": [
"id",
"Count",
"tag.display",
"tag.Enchantments",
"tag.RepairCost"
]
}

View File

@@ -0,0 +1,6 @@
{
"item": "oc2:hard_drive",
"tags": [
"tag.oc2.size"
]
}

View File

@@ -0,0 +1,6 @@
{
"item": "oc2:memory",
"tags": [
"tag.oc2.size"
]
}