复活Bukkit的PDC(2)

前言

在上一篇文章中,我们讨论并尝试了回迁PDC的上层借口,本篇文章将详细讨论CraftBlockEntityState ,CraftEntity ,CraftMetaItem 的中PDC的实现以及NMS改造

CraftBlockEntityState

查看11400的CraftBlockEntityState,

java 复制代码
public class CraftBlockEntityState<T extends TileEntity> extends CraftBlockState implements org.bukkit.block.TileState

由于其实现了TileState ,所以它实现了getPersistentDataContainer() 方法

java 复制代码
@Override
public CraftPersistentDataContainer getPersistentDataContainer() {
    return this.getSnapshot().persistentDataContainer;
}

查看getSnapshot()方法

java 复制代码
// gets the cloned TileEntity which is used to store the captured data
protected T getSnapshot() {
    return snapshot;
}

由其注释我们发现此方法返回一个NMS TileEntity实例

PDC实例

深入NMS TileEntity类,我们发现了CraftBukkit改造增加的PDC实例

java 复制代码
// CraftBukkit start - data containers
private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
public CraftPersistentDataContainer persistentDataContainer;
// CraftBukkit end

它在TileEntity的构造函数中初始化(注:原PDC在load(NBTTagCompound nbttagcompound)函数中初始化,在后期版本的Paper中将其优化为了构造函数中初始化,本文以Paper的声明方式为主)

java 复制代码
public TileEntity() {
    ......
    this.persistentDataContainer = new DelegateCraftPersistentDataContainer(DATA_TYPE_REGISTRY); // Paper - always init
    ......
}

PDC如何存取数据?

查询11400中PDC的实例,我们发现

  • 在TileEntity数据通过save(NBTTagCompound nbttagcompound)保存为NBT时,CraftBukkit修改了保存方法,使PDC中的数据以PublicBukkitValues -> NBTTagCompound形式的PDC数据 (键值对)保存到了Tile Entity的NBTTagCompound中

  • 在TileEntity的load(NBTTagCompound nbttagcompound) 函数加载NBT数据时,CraftBukkit修改了它,使其读取PublicBukkitValues 键的数据并存入PDC实例中

于是相似的,我们可以将上述操作在11202的NMS TileEntity中实现,其中

  • load(NBTTagCompound nbttagcompound) 函数的反混淆名没有变化

  • save(NBTTagCompound nbttagcompound) 函数的在此时还没有反混淆名,为c(NBTTagCompound nbttagcompound)

java 复制代码
public void load(NBTTagCompound nbttagcompound) {
    ...
    // CraftBukkit start - read container
    this.persistentDataContainer.clear(); // Paper - clear instead of init

    NBTTagCompound persistentDataTag = nbttagcompound.getCompound("PublicBukkitValues");
    if (persistentDataTag != null) {
        this.persistentDataContainer.putAll(persistentDataTag);
    }
    // CraftBukkit end
    ...
}
private NBTTagCompound c(NBTTagCompound nbttagcompound) {
    ...
        // CraftBukkit start - store container
        if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
            nbttagcompound.set("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
        }
        // CraftBukkit end
        return nbttagcompound;
    }
    ...
}

至此对CraftBlockEntityState 的PDC改造已无大碍

CraftEntity

如法炮制上方的逻辑,我们发现了CraftEntity 中的PDC实例(没错它不在NMS类中,而是在CraftBukkit的实现类里),和以下三个方法

java 复制代码
@Override
public CraftPersistentDataContainer getPersistentDataContainer() {
    return persistentDataContainer;
}

public void storeBukkitValues(NBTTagCompound c) {
    if (!this.persistentDataContainer.isEmpty()) {
        c.set("BukkitValues", this.persistentDataContainer.toTagCompound());
    }
}

public void readBukkitValues(NBTTagCompound c) {
    NBTTagCompound base = c.getCompound("BukkitValues");
    if (base != null) {
        this.persistentDataContainer.putAll(base);
    }
}

下面的两个方法即为存读PDC数据的方法,它们操作的对象应为为11200 NMS Entity类的save(NBTTagCompound nbttagcompound)f(NBTTagCompound nbttagcompound) (即load方法)

java 复制代码
public NBTTagCompound save(NBTTagCompound nbttagcompound) {
  ...
  // CraftBukkit start - stores eventually existing bukkit values
            if (this.bukkitEntity != null) {
                this.getBukkitEntity().storeBukkitValues(nbttagcompound);
            }
            // CraftBukkit end
  ...
}

public void f(NBTTagCompound nbttagcompound) {
  ...
  this.getBukkitEntity().readBukkitValues(nbttagcompound);
  ...
}

CraftMetaItem

!!注:这部分CraftBukkit在11400和11202的实现有很大不同,笔者在此部分不在比较11400与11200的差异,而直接给出实现思路

PDC实例

声明位于CraftMetaItem中,与CraftEntity相同

初始化

  • 在复制构造函数**CraftMetaItem(CraftMetaItem meta) **中
java 复制代码
this.persistentDataContainer.putAll(meta.persistentDataContainer.getTagsCloned())

这是11605Paper中的优化方法,旨在实现PDC中所有数据的完全深拷贝

  • 在构造函数CraftMetaItem(NBTTagCompound tag)
java 复制代码
...
if (tag.hasKey(BUKKIT_CUSTOM_TAG.NBT)) {
    NBTTagCompound compound = tag.getCompound(BUKKIT_CUSTOM_TAG.NBT);
    Set<String> keys = compound.c();
    for (String key : keys) {
        persistentDataContainer.put(key, compound.get(key).clone());
    }
}
...

其中

java 复制代码
static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues");

在构造函数**CraftMetaItem(Map<String, Object> map) **中

java 复制代码
Object nbtMap = SerializableMeta.getObject(Object.class, map, BUKKIT_CUSTOM_TAG.BUKKIT, true); // We read both legacy maps and potential modern snbt strings here
if (nbtMap != null) {
    this.persistentDataContainer.putAll((NBTTagCompound) CraftNBTTagConfigSerializer.deserialize(nbtMap));
}

注:序列化器**CraftNBTTagSerializer**将在下一章讲到

存取

在保存NBT方法applyToItem(NBTTagCompound itemTag) 最后

java 复制代码
if (!persistentDataContainer.isEmpty()) {
    NBTTagCompound bukkitCustomCompound = new NBTTagCompound();
    Map<String, NBTBase> rawPublicMap = persistentDataContainer.getRaw();

    for (Map.Entry<String, NBTBase> nbtBaseEntry : rawPublicMap.entrySet()) {
        bukkitCustomCompound.set(nbtBaseEntry.getKey(), nbtBaseEntry.getValue());
    }
    itemTag.set(BUKKIT_CUSTOM_TAG.NBT, bukkitCustomCompound);
}

在序列化方法**serialize(ImmutableMap.Builder<String, Object> builder) **最后

java 复制代码
if (!persistentDataContainer.isEmpty()) { // Store custom tags, wrapped in their compound
    builder.put(BUKKIT_CUSTOM_TAG.BUKKIT, persistentDataContainer.serialize());
}

#其他

我们还需要将对应的逻辑添加到isEmpty()equalsCommon(CraftMetaItem that)applyHash() 方法中,它们分别用于判断ItemMeta是否为空,和比较两个ItemMeta是否相等

别忘了!

还没完!在测试中,它并不会正确的处理PDC数据的序列化,事实上,笔者发现PDC中的数据除了正常存在于PublicBukkitValues 中,还被序列化进了internal 键里,此键专门用于存储内部字段的数据,比如玩家头颅数据等。重复序列化的数据在使得ItemMeta数据变得臃肿的同时,也将影响到两个ItemMeta是否相等的比较(因为一些ItemMeta是不通过物品创建的,它们序列化出的数据中不存在internal键)

最后,笔者发现,应将

java 复制代码
static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues");

添加进getHandledTags() 方法,

java 复制代码
HANDLED_TAGS.addAll(Arrays.asList(
                        ...
                        BUKKIT_CUSTOM_TAG.NBT,
                        ...
                ));

这样在序列化时,它便不会被当作内部数据处理

最后

在本篇文章中,我们探讨了PDC在CraftBlockEntityState ,CraftEntity ,CraftMetaItem 以及NMS中的实现,在下一章中我们将分析PDC数据的序列化/反序列化,深入MC数据字段序列化的核心MojangsonParser,使其数据能够正确的被保存和读取

此外,本篇并没有详细也不可能讲解所有的方法,但可保证囊括大多数的方法,其余请读者自行探究

游客

全部评论 (0)

暂无评论,快来抢沙发吧~