Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read only optimization #236

Merged
merged 4 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions item-nbt-api/src/main/java/de/tr7zw/changeme/nbtapi/NBT.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private NBT() {
* @return The function is being returned.
*/
public static <T> T get(ItemStack item, Function<ReadableNBT, T> getter) {
return getter.apply(new NBTItem(item));
return getter.apply(new NBTItem(item, false, true));
}

/**
Expand All @@ -48,7 +48,7 @@ public static <T> T get(ItemStack item, Function<ReadableNBT, T> getter) {
* @return The NBTEntity class is being returned.
*/
public static <T> T get(Entity entity, Function<ReadableNBT, T> getter) {
return getter.apply(new NBTEntity(entity));
return getter.apply(new NBTEntity(entity, true));
}

/**
Expand All @@ -62,7 +62,7 @@ public static <T> T get(Entity entity, Function<ReadableNBT, T> getter) {
* @return The return type is the same as the type of the getter function.
*/
public static <T> T get(BlockState blockState, Function<ReadableNBT, T> getter) {
return getter.apply(new NBTTileEntity(blockState));
return getter.apply(new NBTTileEntity(blockState, true));
}

/**
Expand Down Expand Up @@ -130,7 +130,11 @@ public static void modify(ItemStack item, Consumer<ReadWriteItemNBT> consumer) {
* @return The return type is the same as the return type of the function.
*/
public static <T> T modify(Entity entity, Function<ReadWriteNBT, T> function) {
return function.apply(new NBTEntity(entity));
NBTEntity nbtEnt = new NBTEntity(entity);
NBTContainer cont = new NBTContainer(nbtEnt.getCompound());
T ret = function.apply(cont);
nbtEnt.setCompound(cont.getCompound());
return ret;
}

/**
Expand All @@ -141,7 +145,10 @@ public static <T> T modify(Entity entity, Function<ReadWriteNBT, T> function) {
* @param consumer The consumer that will be called with the NBTEntity.
*/
public static void modify(Entity entity, Consumer<ReadWriteNBT> consumer) {
consumer.accept(new NBTEntity(entity));
NBTEntity nbtEnt = new NBTEntity(entity);
NBTContainer cont = new NBTContainer(nbtEnt.getCompound());
consumer.accept(cont);
nbtEnt.setCompound(cont.getCompound());
}

/**
Expand Down Expand Up @@ -177,7 +184,11 @@ public static void modifyPersistentData(Entity entity, Consumer<ReadWriteNBT> co
* @return The return type is the same as the return type of the function.
*/
public static <T> T modify(BlockState blockState, Function<ReadWriteNBT, T> function) {
return function.apply(new NBTTileEntity(blockState));
NBTTileEntity blockEnt = new NBTTileEntity(blockState);
NBTContainer cont = new NBTContainer(blockEnt.getCompound());
T ret = function.apply(cont);
blockEnt.setCompound(cont);
return ret;
}

/**
Expand All @@ -190,7 +201,10 @@ public static <T> T modify(BlockState blockState, Function<ReadWriteNBT, T> func
* takes a ReadWriteNBT and does something with it.
*/
public static void modify(BlockState blockState, Consumer<ReadWriteNBT> consumer) {
consumer.accept(new NBTTileEntity(blockState));
NBTTileEntity blockEnt = new NBTTileEntity(blockState);
NBTContainer cont = new NBTContainer(blockEnt.getCompound());
consumer.accept(cont);
blockEnt.setCompound(cont);
}

/**
Expand Down
29 changes: 29 additions & 0 deletions item-nbt-api/src/main/java/de/tr7zw/changeme/nbtapi/NBTEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@
public class NBTEntity extends NBTCompound {

private final Entity ent;
private final boolean readonly;
private final Object compound;

/**
* @param entity Any valid Bukkit Entity
* @param readonly Readonly makes a copy at init, only reading from that copy
*/
protected NBTEntity(Entity entity, boolean readonly) {
super(null, null);
if (entity == null) {
throw new NullPointerException("Entity can't be null!");
}
this.readonly = readonly;
ent = entity;
if (readonly) {
this.compound = getCompound();
} else {
this.compound = null;
}
}

/**
* @param entity Any valid Bukkit Entity
Expand All @@ -28,18 +48,27 @@ public NBTEntity(Entity entity) {
if (entity == null) {
throw new NullPointerException("Entity can't be null!");
}
this.readonly = false;
this.compound = null;
ent = entity;
}

@Override
public Object getCompound() {
// this runs before async check, since it's just a copy
if (readonly && compound != null) {
return compound;
}
if (!Bukkit.isPrimaryThread())
throw new NbtApiException("Entity NBT needs to be accessed sync!");
return NBTReflectionUtil.getEntityNBTTagCompound(NBTReflectionUtil.getNMSEntity(ent));
}

@Override
protected void setCompound(Object compound) {
if (readonly) {
throw new NbtApiException("Tried setting data in read only mode!");
}
if (!Bukkit.isPrimaryThread())
throw new NbtApiException("Entity NBT needs to be accessed sync!");
NBTReflectionUtil.setEntityNBTTag(compound, NBTReflectionUtil.getNMSEntity(ent));
Expand Down
32 changes: 32 additions & 0 deletions item-nbt-api/src/main/java/de/tr7zw/changeme/nbtapi/NBTItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import de.tr7zw.changeme.nbtapi.iface.ReadWriteItemNBT;
import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBT;
import de.tr7zw.changeme.nbtapi.iface.ReadableNBT;
import de.tr7zw.changeme.nbtapi.utils.nmsmappings.ClassWrapper;
import de.tr7zw.changeme.nbtapi.utils.nmsmappings.ReflectionMethod;

/**
Expand All @@ -22,6 +23,7 @@ public class NBTItem extends NBTCompound implements ReadWriteItemNBT {

private ItemStack bukkitItem;
private boolean directApply;
private boolean readOnly;
private ItemStack originalSrcStack = null;

/**
Expand All @@ -33,6 +35,30 @@ public NBTItem(ItemStack item) {
this(item, false);
}

/**
* @param item
* @param directApply
* @param readOnly When turned on, no copy of the source item is created.
* Modifying the stack in that case is not valid! Also
* overwrites directApply
*/
protected NBTItem(ItemStack item, boolean directApply, boolean readOnly) {
super(null, null);
if (item == null || item.getType() == Material.AIR || item.getAmount() <= 0) {
throw new NullPointerException("ItemStack can't be null/air/amount of 0! This is not a NBTAPI bug!");
}
if (readOnly) {
bukkitItem = item;
this.readOnly = true;
} else {
this.directApply = directApply;
bukkitItem = item.clone();
if (directApply) {
this.originalSrcStack = item;
}
}
}

/**
* Constructor for NBTItems. The ItemStack will be cloned! If directApply is
* true, all changed will be mapped to the original item. Changes to the NBTItem
Expand All @@ -55,11 +81,17 @@ public NBTItem(ItemStack item, boolean directApply) {

@Override
public Object getCompound() {
if (readOnly && ClassWrapper.CRAFT_ITEMSTACK.getClazz().isAssignableFrom(bukkitItem.getClass())) {
return NBTReflectionUtil.getItemRootNBTTagCompound(NBTReflectionUtil.getCraftItemHandle(bukkitItem));
}
return NBTReflectionUtil.getItemRootNBTTagCompound(ReflectionMethod.ITEMSTACK_NMSCOPY.run(null, bukkitItem));
}

@Override
protected void setCompound(Object compound) {
if (readOnly) {
throw new NbtApiException("Tried setting data in read only mode!");
}
Object stack = ReflectionMethod.ITEMSTACK_NMSCOPY.run(null, bukkitItem);
ReflectionMethod.ITEMSTACK_SET_TAG.run(stack, compound);
bukkitItem = (ItemStack) ReflectionMethod.ITEMSTACK_BUKKITMIRROR.run(null, stack);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;

import de.tr7zw.changeme.nbtapi.utils.GsonWrapper;
Expand All @@ -32,13 +33,20 @@
public class NBTReflectionUtil {

private static Field field_unhandledTags = null;
private static Field field_handle = null;

static {
try {
field_unhandledTags = ClassWrapper.CRAFT_METAITEM.getClazz().getDeclaredField("unhandledTags");
field_unhandledTags.setAccessible(true);
} catch (NoSuchFieldException e) {

}
try {
field_handle = ClassWrapper.CRAFT_ITEMSTACK.getClazz().getDeclaredField("handle");
field_handle.setAccessible(true);
} catch (NoSuchFieldException e) {

}
}

Expand Down Expand Up @@ -96,6 +104,21 @@ public static Object writeNBT(Object nbt, OutputStream stream) {
}
}

/**
* Gets the nms handle ItemStack from a CraftItemStack. Passing Spigot
* ItemStacks will cause an error!
*
* @param item
* @return
*/
public static Object getCraftItemHandle(ItemStack item) {
try {
return field_handle.get(item);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new NbtApiException("Error getting handle from " + item.getClass(), e);
}
}

/**
* Writes a Compound to an OutputStream
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@
public class NBTTileEntity extends NBTCompound {

private final BlockState tile;
private final boolean readonly;
private final Object compound;

/**
* @param tile BlockState from any TileEntity
* @param readonly Readonly makes a copy at init, only reading from that copy
*/
public NBTTileEntity(BlockState tile, boolean readonly) {
super(null, null);
if (tile == null || (MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_8_R3) && !tile.isPlaced())) {
throw new NullPointerException("Tile can't be null/not placed!");
}
this.tile = tile;
this.readonly = readonly;
if (readonly) {
this.compound = getCompound();
} else {
this.compound = null;
}
}

/**
* @param tile BlockState from any TileEntity
Expand All @@ -29,18 +49,27 @@ public NBTTileEntity(BlockState tile) {
if (tile == null || (MinecraftVersion.isAtLeastVersion(MinecraftVersion.MC1_8_R3) && !tile.isPlaced())) {
throw new NullPointerException("Tile can't be null/not placed!");
}
this.readonly = false;
this.compound = null;
this.tile = tile;
}

@Override
public Object getCompound() {
// this runs before async check, since it's just a copy
if (readonly && compound != null) {
return compound;
}
if (!Bukkit.isPrimaryThread())
throw new NbtApiException("BlockEntity NBT needs to be accessed sync!");
return NBTReflectionUtil.getTileEntityNBTTagCompound(tile);
}

@Override
protected void setCompound(Object compound) {
if (readonly) {
throw new NbtApiException("Tried setting data in read only mode!");
}
if (!Bukkit.isPrimaryThread())
throw new NbtApiException("BlockEntity NBT needs to be accessed sync!");
NBTReflectionUtil.setTileEntityNBTTagCompound(tile, compound);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public void test() throws Exception {
nbt.setString("SomeKey", "SomeValue");
return nbt.getString("SomeKey");
});
baseItem = NBT.itemStackFromNBT(NBT.itemStackToNBT(baseItem)); // trick to force the item to be "real", not a
// Spigot only item
String outside = NBT.get(baseItem, nbt -> nbt.getString("SomeKey"));
if (!new NBTItem(baseItem).hasTag("SomeKey")) {
throw new NbtApiException("The data was not applied!");
Expand Down