Resource A guide to 1.14 - PersistentDataHolder API

Discussion in 'Spigot Plugin Development' started by LynxPlay, May 3, 2019.

  1. Hey there,

    after 1.13.2 already got the new CustomItemTagContainer API, 1.14 is expanding on this new MetaData API and we will look into it a bit more, in this post.

    My previous post can be found here and you should really check it out for more examples.
    As this post will expand on it, I will simply copy and adapt most of the content found in the first post.

    First of all, the old API has been deprecated as it is outdated (simply by having the wrong name) and it is now replaced by the PersistentDataHolder API, which applies to ItemMeta, TileEntities and Entities.

    The actual API calls have changed a bit as well so I will write another quick introduction to the API:
    First of all the few naming changes:
    Code (Java):
    setCustomTag(...) -> set(...)
    hasCustomTag(...) -> has(...)
    getCustomTag(...) -> get(...)
    getOrDefault() has been added which ,well, gets the value or returns the provided default
    The PersistentDataContainer is again the main entry point. It is capable of storing primitive values in it, which are stored in the minecraft entity, hence is not lost when the server restarts.

    How do you store values in a PersistentDataConatiner ?
    To store data in the container, you will first of all need a NamespacedKey to identify the namespace your value will be stored under. As the NamespacedKey needs an instance of your plugin, you cannot use this API to overwrite minecraft NBT values.

    Before we can actually save a value, we need to talk about the PersistentDataType. This interface defines an adapter for your input data, which converts it into a primitive value the PersistentDataContainer can store. In this example we will try to store a double on the DataContainer. As this is already a primitive type, we can use the predefined PersistentDataType.DOUBLE instance.

    Examples
    For this example we will use an ItemStack, but I'll talk about how to use this on entites and tile entities later.

    Code (Java):
    ItemStack itemStack = ...;
    NamespacedKey key = new NamespacedKey(pluginInstance, "our-custom-key");
    ItemMeta itemMeta = itemStack.getItemMeta();
    itemMeta.getPersistentDataContainer().set(key, PersistentDataType.DOUBLE, Math.PI);
    itemStack.setItemMeta(itemMeta);
    In this example we stored the value of PI in the container instance, and by feeding the ItemMeta back into the ItemStack, we stored it even over a restart.

    To now retrieve the stored value again we can use:

    Code (Java):
    ItemStack itemStack = ...;
    NamespacedKey key = new NamespacedKey(pluginInstance, "our-custom-key");
    ItemMeta itemMeta = itemStack.getItemMeta();
    PersistentDataContainer container = itemMeta.getPersistentDataContainer();

    if(container.has(key , PersistentDataType.DOUBLE)) {
        double foundValue = container.get(key, PersistentDataType.DOUBLE);
    }
    .

    Node that, if you store the variable instantly in a primitive type, and you did not check if the key and type exist before hand, it will throw a NPE. This is due to auto-unboxing of the original wrapper values the get method returns.

    Custom ItemTagTypes and how to use them ?
    If you have bigger objects you want to store on your PersistentDataContainer, like UUIDs. For this you can simply create an class that implements the PersistentDataType and use an instance of that class as your PersistentDataType. Note that the returned primitive type has to be of one of the originally provided types in the PersistentDataType interface.

    An example for storing and retrieving a UUID would look like this:
    Code (Java):
    public class UUIDDataType implements PersistentDataType<byte[], UUID> {

             @Override
             public Class<byte[]> getPrimitiveType() {
                 return byte[].class;
             }

             @Override
             public Class<UUID> getComplexType() {
                 return UUID.class;
             }

             @Override
             public byte[] toPrimitive(UUID complex, PersistentDataAdapterContext context) {
                 ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
                 bb.putLong(complex.getMostSignificantBits());
                 bb.putLong(complex.getLeastSignificantBits());
                 return bb.array();
             }

             @Override
             public UUID fromPrimitive(byte[] primitive, PersistentDataAdapterContext context) {
                 ByteBuffer bb = ByteBuffer.wrap(primitive);
                 long firstLong = bb.getLong();
                 long secondLong = bb.getLong();
                 return new UUID(firstLong, secondLong);
             }
         }
    A more complex example for storing for example String[] can be found here.

    These own implementations can simply be passed instead of a constant PersistentDataType instance:
    Code (Java):
    container.set(key, new UUIDDataType(), uuid);
    Storing on entities:
    Using this API on entities is as easy as you would expect:
    Code (Java):
    entity.getPersistentDataContainer().set(...);
    Storing on tile entities:
    Let's say we have a sign block state:
    Code (Java):
    Sign sign = (Sign) block.getState();
    sign.getPersistentDataContainer().set(namespacedKey, PersistentDataType.STRING, "some data");
    sign.update();
    Note that you need to update the block state to store the edited values in the tile entity!

    I hope this small tutorial helped you :) Have fun storing stuff in the game.
     
    • Useful x 35
    • Informative x 7
    • Like x 5
    • Winner x 5
    • Friendly x 1
    • Creative x 1
  2. A very useful thread =D I think you should add the Resource tag in the title ;)
     
  3. Tadaa xD Sorry I had no idea those existed
     
  4. Choco

    Moderator

    Ideally you'll want to keep these types as constants (as are the default types) so that no more than a single instance is being created. Alternatively, a singleton approach would also be encouraged here.
     
  5. Agreed, there is definitely potential for something like:

    Code (Java):
    interface MyDataTypes {
        public static final PersistentDataType<byte[],UUID> UUID = new UUIDDataType();
    }
     
  6. I have a question: how to add more tags
    Because I set only one key
     
  7. What do you mean?
     
    • Winner Winner x 2
    • Useful Useful x 2
  8. Couldn't you use the PersistentDataType.TAG_CONTAINER to save this data in a better way?
     
  9. No, I wanted to directly serialise the Object into the tag representation. I could have done it by wrapping the process of serialisation, but then there is no reason to allow own custom DataTagTypes.
     
    • Like Like x 1
  10. Oh sorry I misunderstood your post, I see what you did now, amazing example
     
  11. Im quite new to making plugins so im not too sure about this but could you use this to save a custom gui with its items in it?
     
  12. first time i am seeing this kind of use for interface, made the same but with singleton:

    Code (Java):
    public class PersistenceGemBoxDataTypes {
        private PersistenceGemBoxDataTypes() {}

        public static final PersistentDataType<byte[], java.util.UUID> UUID = new UUIDDataType();
    }
     
    Would be very helpful if someone could explain the differences between those two ways of doing it,
    and which is more preferable.
     
  13. An interface is not instantiable but does not allow private methods
    (Technically, your example is no Singleton thou)
     
  14. thank you