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.
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| Part | Description |
|---|---|
namespace | Namespace, typically the plugin or module name (e.g., emaki) |
path | Data path, using . to separate hierarchy levels |
field | Final 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
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
// 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
boolean hasLevel = partition.has(itemStack, "level", PersistentDataType.INTEGER);
boolean hasOwner = partition.has(itemStack, "owner", PersistentDataType.STRING);remove — Delete Data
partition.remove(itemStack, "level");
partition.remove(itemStack, "exp");writeBlob — Write Binary Data
Used for storing serialized complex objects:
byte[] data = serializeMyData(myObject);
partition.writeBlob(itemStack, "complex_data", data);readBlob — Read Binary Data
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:
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
// Create a YAML-based codec
SnapshotCodec codec = SnapshotCodec.yaml();
// Serialize
byte[] bytes = codec.encode(snapshot);
// Deserialize
EmakiItemLayerSnapshot restored = codec.decode(bytes);Combined with PDC
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:
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.
// 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| Method | Description |
|---|---|
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.
# 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.