# Codec and Serialization System The Hytale server uses a powerful codec system for data serialization and deserialization. This system is used throughout the codebase for configuration, network packets, asset definitions, and data persistence. ## Overview The codec system provides: - Type-safe serialization/deserialization - Support for JSON and BSON formats - Validation and schema generation - Composable codecs for complex types - Builder pattern for object construction ## Core Concepts ### Codec Interface ```java public interface Codec { T decode(DataInput input); void encode(T value, DataOutput output); } ``` ### DataInput/DataOutput Codecs work with abstract `DataInput` and `DataOutput` interfaces that can represent different formats (JSON, BSON, etc.). ## Primitive Codecs The `Codec` class provides built-in codecs for primitive types: ```java // String Codec.STRING // "hello" // Numbers Codec.INTEGER // 42 Codec.LONG // 123456789L Codec.FLOAT // 3.14f Codec.DOUBLE // 3.14159 // Boolean Codec.BOOLEAN // true/false // Byte arrays Codec.BYTE_ARRAY // [1, 2, 3] ``` ## Collection Codecs ### Lists ```java // List of strings Codec> stringList = Codec.STRING.listOf(); // List of integers Codec> intList = Codec.INTEGER.listOf(); // List of custom objects Codec> objectList = MyObject.CODEC.listOf(); ``` ### Sets ```java // Set of strings Codec> stringSet = Codec.STRING.setOf(); ``` ### Maps ```java // Map with string keys Codec> stringToInt = Codec.mapOf(Codec.STRING, Codec.INTEGER); // Map with custom key type Codec> playerMap = Codec.mapOf(UUID_CODEC, PlayerData.CODEC); ``` ### Arrays ```java // Array codec Codec stringArray = Codec.arrayOf(Codec.STRING, String[]::new); ``` ## BuilderCodec `BuilderCodec` is the primary way to create codecs for complex objects: ### Basic Usage ```java public class Person { public static final Codec CODEC = BuilderCodec.of(Person::new) .with("name", Codec.STRING, p -> p.name) .with("age", Codec.INTEGER, p -> p.age) .with("email", Codec.STRING, p -> p.email) .build(); private final String name; private final int age; private final String email; private Person(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } } ``` ### With Default Values ```java public static final Codec CODEC = BuilderCodec.of(Settings::new) .with("volume", Codec.FLOAT, s -> s.volume, 1.0f) // Default: 1.0 .with("muted", Codec.BOOLEAN, s -> s.muted, false) // Default: false .with("language", Codec.STRING, s -> s.language, "en") // Default: "en" .build(); ``` ### Optional Fields ```java public static final Codec CODEC = BuilderCodec.of(User::new) .with("username", Codec.STRING, u -> u.username) .withOptional("nickname", Codec.STRING, u -> u.nickname) // May be absent .build(); ``` ### Nested Objects ```java public class Address { public static final Codec
CODEC = BuilderCodec.of(Address::new) .with("street", Codec.STRING, a -> a.street) .with("city", Codec.STRING, a -> a.city) .with("zip", Codec.STRING, a -> a.zip) .build(); // ... } public class Customer { public static final Codec CODEC = BuilderCodec.of(Customer::new) .with("name", Codec.STRING, c -> c.name) .with("address", Address.CODEC, c -> c.address) .build(); // ... } ``` ## KeyedCodec For objects that have a key/identifier: ```java public class ItemType { public static final KeyedCodec KEYED_CODEC = KeyedCodec.of( "id", Codec.STRING, ItemType::getId, ItemType.CODEC ); // ... } ``` ## Codec Transformations ### Mapping Values ```java // Transform between types Codec UUID_CODEC = Codec.STRING.xmap( UUID::fromString, // decode: String -> UUID UUID::toString // encode: UUID -> String ); // Transform integers to enum Codec ENUM_CODEC = Codec.INTEGER.xmap( i -> MyEnum.values()[i], MyEnum::ordinal ); ``` ### Validation ```java // Validate during decode Codec PORT_CODEC = Codec.INTEGER.validate( port -> port > 0 && port < 65536, "Port must be between 1 and 65535" ); // Clamp values Codec VOLUME_CODEC = Codec.FLOAT.clamp(0.0f, 1.0f); ``` ### Enum Codecs ```java // Automatic enum codec Codec GAME_MODE_CODEC = Codec.enumCodec(GameMode.class); // Custom enum serialization Codec DIRECTION_CODEC = Codec.STRING.xmap( Direction::valueOf, Direction::name ); ``` ## Complex Examples ### Polymorphic Types ```java // For types with multiple implementations public interface Shape { Codec CODEC = Codec.dispatch( "type", Codec.STRING, shape -> shape.getType(), type -> switch (type) { case "circle" -> Circle.CODEC; case "rectangle" -> Rectangle.CODEC; default -> throw new IllegalArgumentException("Unknown shape: " + type); } ); String getType(); } public class Circle implements Shape { public static final Codec CODEC = BuilderCodec.of(Circle::new) .with("radius", Codec.DOUBLE, c -> c.radius) .build(); @Override public String getType() { return "circle"; } // ... } ``` ### Recursive Types ```java public class TreeNode { public static final Codec CODEC = BuilderCodec.of(TreeNode::new) .with("value", Codec.STRING, n -> n.value) .with("children", Codec.lazy(() -> TreeNode.CODEC.listOf()), n -> n.children, List.of()) .build(); private final String value; private final List children; // ... } ``` ### Record Types (Java 16+) ```java public record Point(double x, double y, double z) { public static final Codec CODEC = BuilderCodec.of(Point::new) .with("x", Codec.DOUBLE, Point::x) .with("y", Codec.DOUBLE, Point::y) .with("z", Codec.DOUBLE, Point::z) .build(); } ``` ## Vector and Math Codecs The math library provides codecs for common types: ```java // 3D Vector (double) Vector3d.CODEC // {"x": 1.0, "y": 2.0, "z": 3.0} // 3D Vector (int) Vector3i.CODEC // {"x": 1, "y": 2, "z": 3} // 2D Vector Vector2d.CODEC // {"x": 1.0, "y": 2.0} ``` ## Working with JSON ### Encoding to JSON ```java // Create JSON output JsonDataOutput output = new JsonDataOutput(); MyObject.CODEC.encode(myObject, output); String json = output.toJsonString(); ``` ### Decoding from JSON ```java // Parse JSON input JsonDataInput input = JsonDataInput.fromString(jsonString); MyObject obj = MyObject.CODEC.decode(input); ``` ## Working with BSON ### BSON Encoding/Decoding ```java // BSON output BsonDataOutput output = new BsonDataOutput(); MyObject.CODEC.encode(myObject, output); BsonDocument bson = output.toBsonDocument(); // BSON input BsonDataInput input = new BsonDataInput(bsonDocument); MyObject obj = MyObject.CODEC.decode(input); ``` ## Schema Generation Codecs can generate JSON schemas for documentation: ```java // Generate schema JsonSchema schema = MyObject.CODEC.generateSchema(); String schemaJson = schema.toJson(); ``` ## Best Practices 1. **Make codecs static final** - Codecs are immutable and should be reused 2. **Use BuilderCodec for objects** - It's the most flexible approach 3. **Provide defaults** - Use sensible defaults for optional fields 4. **Validate input** - Use validation to catch errors early 5. **Keep codecs near their classes** - Define codec as a static field in the class 6. **Test serialization roundtrips** - Ensure encode/decode produces identical objects 7. **Use meaningful field names** - JSON keys should be clear and consistent 8. **Handle null carefully** - Use Optional or defaults for nullable fields 9. **Consider versioning** - Plan for schema evolution 10. **Document complex codecs** - Add comments for non-obvious serialization ## Error Handling ### Decode Errors ```java try { MyObject obj = MyObject.CODEC.decode(input); } catch (CodecException e) { // Handle missing fields, wrong types, validation failures logger.error("Failed to decode: " + e.getMessage()); } ``` ### Validation Errors ```java Codec validated = Codec.INTEGER.validate( i -> i > 0, "Value must be positive" ); // Throws CodecException with message "Value must be positive" validated.decode(input); // if input is <= 0 ``` ## Integration with Assets Asset types use codecs for JSON definitions: ```java public class BlockType { public static final Codec CODEC = BuilderCodec.of(BlockType::new) .with("id", Codec.STRING, b -> b.id) .with("name", Codec.STRING, b -> b.name) .with("hardness", Codec.FLOAT, b -> b.hardness, 1.0f) .with("drops", Codec.STRING.listOf(), b -> b.drops, List.of()) .build(); // ... } ``` ## Integration with Packets Network packets use codecs for serialization: ```java public class PlayerPositionPacket implements Packet { public static final Codec CODEC = BuilderCodec.of(PlayerPositionPacket::new) .with("playerId", UUID_CODEC, p -> p.playerId) .with("position", Vector3d.CODEC, p -> p.position) .with("yaw", Codec.FLOAT, p -> p.yaw) .with("pitch", Codec.FLOAT, p -> p.pitch) .build(); // ... } ```