spine-runtimes/tests/haxe-serializer.md
2025-07-30 10:10:49 +02:00

333 lines
13 KiB
Markdown

# Haxe Serializer: Java Getter to Haxe Field/Method Mapping Analysis
## Overview
This document contains a comprehensive analysis of ALL patterns for transforming Java getter calls into appropriate Haxe field/method access, extracted from the java-haxe-diff.md file.
## General Patterns
### 1. Simple Field Access Pattern (Most Common)
Java getter `getX()` maps to Haxe field `x:Type`
**Examples:**
- `getName()``name:String`
- `getDuration()``duration:Float`
- `getTimeScale()``timeScale:Float`
- `getLoop()``loop:Bool`
- `getX()``x:Float`
- `getY()``y:Float`
- `getRotation()``rotation:Float`
- `getScaleX()``scaleX:Float`
- `getScaleY()``scaleY:Float`
- `getShearX()``shearX:Float`
- `getShearY()``shearY:Float`
- `getWidth()``width:Float`
- `getHeight()``height:Float`
- `getColor()``color:Color`
- `getAlpha()``alpha:Float`
- `getMix()``mix:Float`
- `getVisible()``visible:Bool`
- `getIndex()``index:Int`
### 2. Array Field Access Pattern
Java getter `getX()` returns array → Haxe field `x:Array<Type>`
**Examples:**
- `getTimelines()``timelines:Array<Timeline>`
- `getBones()``bones:Array<Int>` or `bones:Array<BoneData>` or `bones:Array<BonePose>` (context-dependent)
- `getChildren()``children:Array<Bone>`
- `getFrames()``frames:Array<Float>`
- `getAttachmentNames()``attachmentNames:Array<String>`
- `getVertices()``vertices:Array<Float>` or `vertices:Array<Array<Float>>` (context-dependent)
- `getEvents()``events:Array<Event>`
- `getDrawOrders()``drawOrders:Array<Array<Int>>`
- `getSlots()``slots:Array<Slot>` or `slots:Array<SlotData>` (context-dependent)
- `getTracks()``tracks:Array<TrackEntry>`
- `getTriangles()``triangles:Array<Int>`
- `getUVs()``uvs:Array<Float>`
- `getRegionUVs()``regionUVs:Array<Float>`
- `getEdges()``edges:Array<Int>`
- `getLengths()``lengths:Array<Float>`
- `getRegions()``regions:Array<TextureRegion>`
- `getAnimations()``animations:Array<Animation>`
- `getSkins()``skins:Array<Skin>`
- `getConstraints()``constraints:Array<Constraint<Dynamic, Dynamic, Dynamic>>` or `constraints:Array<ConstraintData<Dynamic, Dynamic>>`
- `getPhysicsConstraints()``physics:Array<PhysicsConstraint>`
- `getProperties()``properties:Array<FromProperty>`
- `getDeform()``deform:Array<Float>`
### 3. Method Remains Method Pattern
Some getters remain as methods in Haxe, typically those that perform calculations or have side effects.
**Examples:**
- `getFrameCount()``getFrameCount():Int`
- `getFrameEntries()``getFrameEntries():Int`
- `getDuration()``getDuration():Float` (in Timeline classes)
- `getSlotIndex()``getSlotIndex():Int`
- `getBoneIndex()``getBoneIndex():Int`
- `getConstraintIndex()``getConstraintIndex():Int`
- `getData()``getData():T` (where T is the data type)
- `getPose()``getPose():T` (where T is the pose type)
- `getAppliedPose()``getAppliedPose():T`
- `getSetupPose()``getSetupPose():T`
- `getAnimationTime()``getAnimationTime():Float`
- `getTrackComplete()``getTrackComplete():Float`
- `getWorldRotationX()``getWorldRotationX():Float`
- `getWorldRotationY()``getWorldRotationY():Float`
- `getWorldScaleX()``getWorldScaleX():Float`
- `getWorldScaleY()``getWorldScaleY():Float`
- `getAttachments()``getAttachments():Array<SkinEntry>`
- `getOffsetRotation()``getOffsetRotation():Float`
- `getOffsetX()``getOffsetX():Float`
- `getOffsetY()``getOffsetY():Float`
- `getOffsetScaleX()``getOffsetScaleX():Float`
- `getOffsetScaleY()``getOffsetScaleY():Float`
- `getOffsetShearY()``getOffsetShearY():Float`
### 4. Special Property Access Pattern
Some properties use Haxe's property syntax with get/set accessors.
**Examples:**
- `getName()``name(get, never):String` (in Attachment subclasses)
- `getRootBone()``rootBone(get, never):Bone`
- `getScaleY()``scaleY(get, default):Float` (in Skeleton)
### 5. Field Name Variations
Some getters have slight variations in their Haxe field names.
**Examples:**
- `getInt()``intValue:Int`
- `getFloat()``floatValue:Float`
- `getString()``stringValue:String`
- `getUpdateCache()``_updateCache:Array<Dynamic>` (with underscore prefix)
- `getPropertyIds()``propertyIds:Array<String>`
- `getDefaultSkin()``defaultSkin:Skin`
### 6. Type Reference Patterns
Getters that reference other objects.
**Examples:**
- `getParent()``parent:Bone` or `parent:BoneData` (context-dependent)
- `getTarget()``target:Bone` or `target:BoneData` (context-dependent)
- `getSource()``source:Bone` or `source:BoneData` (context-dependent)
- `getAttachment()``attachment:Attachment` or `attachment:VertexAttachment` (context-dependent)
- `getSlot()``slot:Slot` or `slot:SlotData` (context-dependent)
- `getBone()``bone:Bone` or `bone:BoneData` or `bone:BonePose` (context-dependent)
- `getSkin()``skin:Skin`
- `getAnimation()``animation:Animation`
- `getRegion()``region:TextureRegion`
- `getSequence()``sequence:Sequence`
- `getParentMesh()``parentMesh:MeshAttachment`
- `getEndSlot()``endSlot:SlotData`
## Context-Dependent Mappings
### 1. `getBones()` mapping depends on containing class:
- In `Animation`: → `bones:Array<Int>`
- In `BoneData`, `IkConstraintData`, `PathConstraintData`, `TransformConstraintData`, `Skin`: → `bones:Array<BoneData>`
- In `IkConstraint`, `PathConstraint`, `TransformConstraint`: → `bones:Array<BonePose>`
- In `BoundingBoxAttachment`, `ClippingAttachment`, `MeshAttachment`, `PathAttachment`, `VertexAttachment`: → `bones:Array<Int>`
### 2. `getVertices()` mapping depends on containing class:
- In `DeformTimeline`: → `vertices:Array<Array<Float>>` (2D array)
- In `ClippingAttachment`, `MeshAttachment`, `PathAttachment`, `VertexAttachment`: → `vertices:Array<Float>` (1D array)
### 3. `getDuration()` mapping depends on containing class:
- In `Animation`: → `duration:Float` (field)
- In Timeline classes: → `getDuration():Float` (method)
### 4. Special Cases in Timeline Classes:
All Timeline subclasses have these getters as methods:
- `getFrameCount()``getFrameCount():Int`
- `getFrameEntries()``getFrameEntries():Int`
- `getDuration()``getDuration():Float`
- `getPropertyIds()``propertyIds:Array<String>` (field)
- `getFrames()``frames:Array<Float>` (field)
### 5. Special Cases in Constraint Classes:
- `getData()``getData():T` (method returning specific data type)
- `getPose()``getPose():T` (method returning specific pose type)
- `getAppliedPose()``getAppliedPose():T` (method returning specific pose type)
## Type Transformations
### Java to Haxe Type Mappings:
- `int``Int`
- `float``Float`
- `double``Float`
- `boolean``Bool`
- `String``String`
- `Array`/`List``Array<T>`
- `IntArray``Array<Int>` (custom type)
- Object types remain the same (e.g., `Color``Color`)
## Special Edge Cases
### 1. Incomplete Mappings (marked as TODO in the file):
- `BonePose.getInherit()` → TODO
- `BoundingBoxAttachment.getVertices()` → TODO
- `BoundingBoxAttachment.getWorldVerticesLength()` → TODO
- `BoundingBoxAttachment.getTimelineAttachment()` → TODO
- `BoundingBoxAttachment.getId()` → TODO
- `BoundingBoxAttachment.getName()` → TODO
### 2. Inherited Methods:
Some getters are inherited from parent classes and noted as such:
- `getName()` in attachment classes inherits from `Attachment`
- Properties in `FromRotate`, `FromScaleX`, etc. inherit from `FromProperty`
- Properties in constraint data classes inherit from `PosedData`
### 3. Special Skeleton Fields:
- `getUpdateCache()``_updateCache:Array<Dynamic>` (private with underscore)
- `getRootBone()``rootBone(get, never):Bone` (computed property)
- `getScaleY()``scaleY(get, default):Float` (property with default)
### 4. DrawOrderTimeline Exception:
- `getFrameCount()``frameCount:Int` (field instead of method, unlike other timelines)
### 5. Enum and Constant Mappings:
- `getBlendMode()``blendMode:BlendMode`
- `getPositionMode()``positionMode:PositionMode`
- `getSpacingMode()``spacingMode:SpacingMode`
- `getRotateMode()``rotateMode:RotateMode`
- `getMixBlend()``mixBlend:MixBlend`
- `getInherit()``inherit:Inherit`
## Summary
The transformation rules can be categorized as:
1. **Default Rule**: `getX()``x:Type` (lowercase first letter, remove get prefix)
2. **Method Preservation**: Keep as method for calculated values or methods with side effects
3. **Special Properties**: Use Haxe property syntax for computed/readonly properties
4. **Context Awareness**: Same getter can map differently based on containing class
5. **Type Transformation**: Java primitive types map to Haxe equivalents
6. **Special Cases**: Some fields have custom names (e.g., `getInt()``intValue`)
When implementing the Haxe serializer generator, these patterns should be applied in order of specificity:
1. Check for exact class + getter combinations first
2. Check for class-specific patterns (e.g., all Timeline getters)
3. Apply general transformation rules
4. Handle special cases and exceptions
---
# Revised Implementation Plan: Reflection-Based Haxe Serializer
## Key Insight
Instead of maintaining complex mapping tables, we can leverage Haxe's dynamic reflection capabilities to automatically resolve field vs method access at runtime.
## Simplified Architecture
### Core Approach: Runtime Property Resolution
```haxe
private function getPropertyValue(obj:Dynamic, javaGetter:String):Dynamic {
// Extract property name from Java getter
var propName = extractPropertyName(javaGetter); // getName() → "name"
// 1. Try direct field access first (most common case)
if (Reflect.hasField(obj, propName)) {
return Reflect.field(obj, propName);
}
// 2. Try special field variations
var specialNames = getSpecialFieldNames(javaGetter, propName);
for (name in specialNames) {
if (Reflect.hasField(obj, name)) {
return Reflect.field(obj, name);
}
}
// 3. Try method access (for computed properties)
if (Reflect.hasField(obj, javaGetter.replace("()", ""))) {
return Reflect.callMethod(obj, Reflect.field(obj, javaGetter.replace("()", "")), []);
}
// 4. Handle property syntax (get, never)
// This would need special handling or we just access the underlying getter
throw 'Property ${javaGetter} not found on object';
}
```
### Special Name Mappings
Based on the pattern analysis, we only need to handle these special cases:
```haxe
private function getSpecialFieldNames(javaGetter:String, defaultName:String):Array<String> {
return switch(javaGetter) {
case "getInt()": ["intValue"];
case "getFloat()": ["floatValue"];
case "getString()": ["stringValue"];
case "getPhysicsConstraints()": ["physics"];
case "getUpdateCache()": ["_updateCache"];
case "getSetupPose()": ["setup"];
case "getAppliedPose()": ["applied"];
default: [];
}
}
```
## Implementation Steps
### Phase 1: Core Reflection System
1. Implement `getPropertyValue` with fallback chain
2. Handle special field name mappings
3. Test with known edge cases
### Phase 2: Type Handling
1. Keep existing Java → Haxe type transformations
2. Use `Dynamic` for runtime resolution
3. Cast results when needed for type safety
### Phase 3: Code Generation
1. Generate simpler code using reflection helpers
2. No need for complex getter-to-field mappings
3. Handle enums with runtime type checking
## Advantages
1. **Simplicity**: No need to parse mapping files or maintain lookup tables
2. **Robustness**: Automatically handles API changes
3. **Correctness**: Runtime resolution ensures we get the right value
4. **Maintainability**: Minimal special cases to maintain
## Trade-offs
1. **Performance**: Reflection is slower than direct access (acceptable for serialization)
2. **Type Safety**: Less compile-time checking (mitigated by runtime tests)
3. **Debugging**: Harder to trace field access (can add logging)
## Example Generated Code
```haxe
private function writeAnimation(obj:Animation):Void {
// ... cycle detection ...
json.writeObjectStart();
json.writeName("type");
json.writeValue("Animation");
// Use reflection for all properties
json.writeName("timelines");
writeArray(getPropertyValue(obj, "getTimelines()"), writeTimeline);
json.writeName("duration");
json.writeValue(getPropertyValue(obj, "getDuration()"));
json.writeName("bones");
writeIntArray(getPropertyValue(obj, "getBones()"));
json.writeName("name");
json.writeValue(getPropertyValue(obj, "getName()"));
json.writeObjectEnd();
}
```
## Summary
This reflection-based approach eliminates the complexity of maintaining mapping tables while preserving correctness. The patterns we analyzed show that most getters follow predictable conventions, with only a handful of special cases that can be handled with a simple switch statement.