Skip to content

PDC Service (PersistentDataContainer Service)

The PDC service provides a partitioned persistent storage solution for EmakiCoreLib based on Bukkit's PersistentDataContainer, supporting structured data read/write, batch operations, snapshot encoding/decoding, and data signing.

Core Concepts

PdcService

PdcService is the entry point for PDC operations, managing all partitions and serving as the single data access interface.

PdcPartition

PdcPartition is a logical partition that isolates different modules' data under their own namespaces to avoid key conflicts.

java
PdcService pdcService = EmakiServiceRegistry.get(PdcService.class);

// Get or create a partition
PdcPartition partition = pdcService.partition("my_module");

Key Format

PDC keys use the namespace:path.field format:

emaki:forge.level          → Forge level
emaki:forge.exp            → Forge experience
emaki:gem.slots.0.type     → Type of the first gem slot
emaki:cooking.recipe_id    → Cooking recipe ID
PartDescription
namespaceNamespace, typically the plugin or module name (e.g., emaki)
pathData path, using . to separate hierarchy levels
fieldFinal field name

Tip

In the key format, : separates the namespace from the path, and . separates path hierarchy levels. The complete key is converted to a Bukkit NamespacedKey.

API Methods

set — Write Data

java
PdcPartition partition = pdcService.partition("forge");

// Write primitive types
partition.set(itemStack, "level", PersistentDataType.INTEGER, 5);
partition.set(itemStack, "exp", PersistentDataType.DOUBLE, 1250.5);
partition.set(itemStack, "owner", PersistentDataType.STRING, "Steve");

get — Read Data

java
// Read data (returns Optional)
Optional<Integer> level = partition.get(itemStack, "level", PersistentDataType.INTEGER);
Optional<Double> exp = partition.get(itemStack, "exp", PersistentDataType.DOUBLE);
Optional<String> owner = partition.get(itemStack, "owner", PersistentDataType.STRING);

// With default value
int lvl = partition.getOrDefault(itemStack, "level", PersistentDataType.INTEGER, 0);

has — Check if Key Exists

java
boolean hasLevel = partition.has(itemStack, "level", PersistentDataType.INTEGER);
boolean hasOwner = partition.has(itemStack, "owner", PersistentDataType.STRING);

remove — Delete Data

java
partition.remove(itemStack, "level");
partition.remove(itemStack, "exp");

writeBlob — Write Binary Data

Used for storing serialized complex objects:

java
byte[] data = serializeMyData(myObject);
partition.writeBlob(itemStack, "complex_data", data);

readBlob — Read Binary Data

java
Optional<byte[]> data = partition.readBlob(itemStack, "complex_data");
data.ifPresent(bytes -> {
    MyObject obj = deserializeMyData(bytes);
    // Use obj
});

batchMutate — Batch Operations

Execute multiple reads and writes in a single operation to reduce PDC access overhead:

java
partition.batchMutate(itemStack, (accessor) -> {
    // Read current values
    int currentLevel = accessor.getOrDefault("level", PersistentDataType.INTEGER, 0);
    double currentExp = accessor.getOrDefault("exp", PersistentDataType.DOUBLE, 0.0);

    // Calculate new values
    int newLevel = currentLevel + 1;
    double newExp = currentExp - requiredExp;

    // Write new values
    accessor.set("level", PersistentDataType.INTEGER, newLevel);
    accessor.set("exp", PersistentDataType.DOUBLE, newExp);
    accessor.set("last_upgrade", PersistentDataType.LONG, System.currentTimeMillis());

    // Delete temporary data
    accessor.remove("temp_bonus");
});

Tip

All operations within batchMutate execute in the same PDC access context, which is more efficient than multiple individual set / get calls. It is recommended when you need to read and write multiple fields simultaneously.

SnapshotCodec

SnapshotCodec provides serialization and deserialization capabilities for layer snapshots, primarily used by the assembly system to store EmakiItemLayerSnapshot in PDC.

YAML Factory Method

java
// Create a YAML-based codec
SnapshotCodec codec = SnapshotCodec.yaml();

// Serialize
byte[] bytes = codec.encode(snapshot);

// Deserialize
EmakiItemLayerSnapshot restored = codec.decode(bytes);

Combined with PDC

java
PdcPartition partition = pdcService.partition("assembly");
SnapshotCodec codec = SnapshotCodec.yaml();

// Write snapshot
byte[] encoded = codec.encode(snapshot);
partition.writeBlob(itemStack, "forge", encoded);

// Read snapshot
Optional<byte[]> data = partition.readBlob(itemStack, "forge");
EmakiItemLayerSnapshot restored = data.map(codec::decode).orElse(null);

Version Migration

SnapshotCodec supports data migration based on schemaVersion:

java
SnapshotCodec codec = SnapshotCodec.yaml()
    .registerMigration(1, 2, (oldData) -> {
        // Migrate from v1 to v2
        oldData.put("new_field", "default_value");
        oldData.remove("deprecated_field");
        return oldData;
    })
    .registerMigration(2, 3, (oldData) -> {
        // Migrate from v2 to v3
        // ...
        return oldData;
    });

SignatureUtil

SignatureUtil provides data signing and verification functionality for detecting whether item data has been tampered with.

java
// Generate signature
String signature = SignatureUtil.sign(snapshotBytes, secretKey);

// Write signature
partition.set(itemStack, "forge.signature", PersistentDataType.STRING, signature);

// Verify signature
Optional<String> storedSig = partition.get(itemStack, "forge.signature", PersistentDataType.STRING);
boolean valid = storedSig
    .map(sig -> SignatureUtil.verify(snapshotBytes, sig, secretKey))
    .orElse(false);

if (!valid) {
    logger.warning("物品数据签名校验失败,可能被篡改!");
}

Signing Process

Raw Data → HMAC-SHA256(data, secretKey) → Base64 Encoding → Signature String
MethodDescription
SignatureUtil.sign(byte[], String)Generate an HMAC-SHA256 signature for the data
SignatureUtil.verify(byte[], String, String)Verify whether the data matches the signature
SignatureUtil.generateKey()Generate a random key

Warning

The signing key (secretKey) should be stored in the server configuration — do not hardcode it in your code. Key leakage will render the signing mechanism ineffective. It is recommended to configure it in config.yml and auto-generate it on first startup.

yaml
# config.yml
security:
  # Data signing key (auto-generated on first startup, do not leak)
  signature_key: "auto_generated_base64_key_here"

Tip

Signature verification is an optional feature. For data that does not require tamper protection, you can skip the signing step to reduce storage overhead. The assembly system enables signature verification for all layer snapshots by default.

Released under the GPL-3.0 License