|
| 1 | +# Patched<T> Type System Documentation |
| 2 | + |
| 3 | +The `Patched<T>` type is a sophisticated runtime patching system for Haxe that allows you to extend existing classes with new fields and methods without modifying the original class definition. It combines runtime flexibility with compile-time safety through macro validation. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **Runtime Field Addition**: Add new fields and methods to existing objects at runtime |
| 8 | +- **Compile-Time Validation**: Macro system validates field access at compile time |
| 9 | +- **Type Safety**: Maintains type safety while allowing dynamic extensions |
| 10 | +- **Multiple Extensions**: Support for multiple patch extensions per object |
| 11 | +- **Custom Getters/Setters**: Advanced property handling with custom accessor functions |
| 12 | +- **Method Patching**: Add new methods with proper `this` binding |
| 13 | +- **Introspection**: Query available fields and extensions |
| 14 | +- **Error Handling**: Comprehensive error reporting for invalid operations |
| 15 | + |
| 16 | +## Basic Usage |
| 17 | + |
| 18 | +### Creating a Patched Object |
| 19 | + |
| 20 | +```haxe |
| 21 | +// Start with any existing object |
| 22 | +var myObject = new SomeClass(); |
| 23 | +
|
| 24 | +// Convert to patched object |
| 25 | +var patched:Patched<SomeClass> = myObject; |
| 26 | +``` |
| 27 | + |
| 28 | +### Adding Extensions |
| 29 | + |
| 30 | +```haxe |
| 31 | +// Define fields and methods for the extension |
| 32 | +patched.addExtension("MyExtension", [ |
| 33 | + { |
| 34 | + name: "newField", |
| 35 | + type: "String", |
| 36 | + isMethod: false, |
| 37 | + defaultValue: "Hello World" |
| 38 | + }, |
| 39 | + { |
| 40 | + name: "newMethod", |
| 41 | + type: "Int->String", |
| 42 | + isMethod: true, |
| 43 | + defaultValue: function(x:Int):String { |
| 44 | + return "Result: " + x; |
| 45 | + } |
| 46 | + } |
| 47 | +]); |
| 48 | +``` |
| 49 | + |
| 50 | +### Accessing Patched Fields |
| 51 | + |
| 52 | +```haxe |
| 53 | +// Get field values |
| 54 | +var fieldValue = patched.getField("newField"); |
| 55 | +
|
| 56 | +// Set field values |
| 57 | +patched.setField("newField", "New Value"); |
| 58 | +
|
| 59 | +// Call methods |
| 60 | +var result = patched.callMethod("newMethod", [42]); |
| 61 | +
|
| 62 | +// Array-style access also works |
| 63 | +var value = patched["newField"]; |
| 64 | +patched["newField"] = "Another Value"; |
| 65 | +``` |
| 66 | + |
| 67 | +## Advanced Features |
| 68 | + |
| 69 | +### Custom Getters and Setters |
| 70 | + |
| 71 | +```haxe |
| 72 | +patched.addExtension("PropertyExtension", [ |
| 73 | + { |
| 74 | + name: "computedProperty", |
| 75 | + type: "String", |
| 76 | + isMethod: false, |
| 77 | + defaultValue: "initial", |
| 78 | + getter: function(currentValue:Dynamic):Dynamic { |
| 79 | + return "Computed: " + currentValue; |
| 80 | + }, |
| 81 | + setter: function(currentValue:Dynamic, newValue:Dynamic):Void { |
| 82 | + trace("Setting property from " + currentValue + " to " + newValue); |
| 83 | + // Custom validation or transformation logic here |
| 84 | + } |
| 85 | + } |
| 86 | +]); |
| 87 | +``` |
| 88 | + |
| 89 | +### Multiple Extensions |
| 90 | + |
| 91 | +```haxe |
| 92 | +// Add multiple extensions to the same object |
| 93 | +patched.addExtension("MathExtension", [ |
| 94 | + { name: "multiplier", type: "Float", isMethod: false, defaultValue: 2.0 }, |
| 95 | + { name: "multiply", type: "Float->Float", isMethod: true, |
| 96 | + defaultValue: function(x:Float) return x * patched.getField("multiplier") } |
| 97 | +]); |
| 98 | +
|
| 99 | +patched.addExtension("StringExtension", [ |
| 100 | + { name: "prefix", type: "String", isMethod: false, defaultValue: "Result:" }, |
| 101 | + { name: "format", type: "String->String", isMethod: true, |
| 102 | + defaultValue: function(s:String) return patched.getField("prefix") + " " + s } |
| 103 | +]); |
| 104 | +``` |
| 105 | + |
| 106 | +### Extension Management |
| 107 | + |
| 108 | +```haxe |
| 109 | +// Check if extension exists |
| 110 | +if (patched.hasExtension("MyExtension")) { |
| 111 | + // Extension is available |
| 112 | +} |
| 113 | +
|
| 114 | +// Get all extension names |
| 115 | +var extensions = patched.getExtensionNames(); |
| 116 | +
|
| 117 | +// Remove an extension |
| 118 | +patched.removeExtension("MyExtension"); |
| 119 | +``` |
| 120 | + |
| 121 | +### Introspection |
| 122 | + |
| 123 | +```haxe |
| 124 | +// Get information about all available fields |
| 125 | +var fieldInfo = patched.getFieldInfo(); |
| 126 | +for (info in fieldInfo) { |
| 127 | + trace('Field: ${info.name} (${info.type}) from ${info.source}'); |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +## Compile-Time Validation |
| 132 | + |
| 133 | +The Patched type system includes macro-based compile-time validation that checks field access at compilation time: |
| 134 | + |
| 135 | +```haxe |
| 136 | +var patched:Patched<SomeClass> = someObject; |
| 137 | +
|
| 138 | +// Add known extension |
| 139 | +patched.addExtension("TestExt", [ |
| 140 | + { name: "validField", type: "String", isMethod: false, defaultValue: "test" } |
| 141 | +]); |
| 142 | +
|
| 143 | +// This works - accessing known base field |
| 144 | +var baseField = patched.getField("existingField"); |
| 145 | +
|
| 146 | +// This works - accessing known patched field |
| 147 | +var patchedField = patched.getField("validField"); |
| 148 | +
|
| 149 | +// This causes a COMPILE-TIME ERROR - field doesn't exist |
| 150 | +var invalid = patched.getField("nonExistentField"); // Error! |
| 151 | +``` |
| 152 | + |
| 153 | +The macro system: |
| 154 | +- Validates field names at compile time |
| 155 | +- Provides helpful error messages with available field suggestions |
| 156 | +- Maintains type safety while allowing runtime flexibility |
| 157 | +- Integrates with Haxe's standard error reporting |
| 158 | + |
| 159 | +## Field Definition Structure |
| 160 | + |
| 161 | +Each field in an extension is defined using the `PatchedField` typedef: |
| 162 | + |
| 163 | +```haxe |
| 164 | +typedef PatchedField = { |
| 165 | + name: String, // Field name |
| 166 | + type: String, // Type description (for documentation) |
| 167 | + isMethod: Bool, // Whether this is a method or property |
| 168 | + ?defaultValue: Dynamic, // Initial value or function |
| 169 | + ?getter: Dynamic->Dynamic, // Custom getter function |
| 170 | + ?setter: Dynamic->Dynamic->Void // Custom setter function |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +### Field Types |
| 175 | + |
| 176 | +- **Properties**: `isMethod: false` - Store and retrieve values |
| 177 | +- **Methods**: `isMethod: true` - Executable functions with arguments |
| 178 | +- **Computed Properties**: Properties with custom getters/setters |
| 179 | +- **Hybrid Fields**: Can act as both property and method depending on usage |
| 180 | + |
| 181 | +## Error Handling |
| 182 | + |
| 183 | +The system provides comprehensive error handling: |
| 184 | + |
| 185 | +```haxe |
| 186 | +try { |
| 187 | + patched.getField("nonExistentField"); |
| 188 | +} catch (e:Dynamic) { |
| 189 | + trace("Field not found: " + e); |
| 190 | +} |
| 191 | +
|
| 192 | +try { |
| 193 | + patched.callMethod("notAMethod", []); |
| 194 | +} catch (e:Dynamic) { |
| 195 | + trace("Method call failed: " + e); |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +Common error scenarios: |
| 200 | +- Accessing non-existent fields |
| 201 | +- Calling non-existent methods |
| 202 | +- Calling properties as methods |
| 203 | +- Setting read-only computed properties |
| 204 | + |
| 205 | +## Integration with Existing Code |
| 206 | + |
| 207 | +The Patched type integrates seamlessly with existing Haxe code: |
| 208 | + |
| 209 | +```haxe |
| 210 | +// Can be used with any existing class |
| 211 | +class MyClass { |
| 212 | + public var originalField:String = "original"; |
| 213 | + public function originalMethod():String return "original method"; |
| 214 | + public function new() {} |
| 215 | +} |
| 216 | +
|
| 217 | +var obj = new MyClass(); |
| 218 | +var patched:Patched<MyClass> = obj; |
| 219 | +
|
| 220 | +// Original functionality still works |
| 221 | +trace(patched.getField("originalField")); // "original" |
| 222 | +trace(patched.callMethod("originalMethod", [])); // "original method" |
| 223 | +
|
| 224 | +// Plus new patched functionality |
| 225 | +patched.addExtension("Enhancement", [...]); |
| 226 | +``` |
| 227 | + |
| 228 | +## Performance Considerations |
| 229 | + |
| 230 | +- **Runtime Overhead**: Field access goes through reflection, so there's some overhead compared to direct access |
| 231 | +- **Memory Usage**: Each patched object stores extension data and field mappings |
| 232 | +- **Compile-Time Benefits**: Macro validation catches errors early, reducing runtime debugging |
| 233 | +- **Lazy Loading**: Extensions are only created when needed |
| 234 | + |
| 235 | +## Best Practices |
| 236 | + |
| 237 | +1. **Use Descriptive Extension Names**: Makes debugging and introspection easier |
| 238 | +2. **Group Related Fields**: Put related fields and methods in the same extension |
| 239 | +3. **Validate Input**: Use custom setters to validate field values |
| 240 | +4. **Document Field Types**: Use meaningful type descriptions in field definitions |
| 241 | +5. **Handle Errors Gracefully**: Always wrap field access in try-catch for production code |
| 242 | +6. **Clean Up Extensions**: Remove unused extensions to save memory |
| 243 | + |
| 244 | +## Use Cases |
| 245 | + |
| 246 | +- **Plugin Systems**: Add functionality to objects without modifying base classes |
| 247 | +- **Runtime Configuration**: Add configuration fields based on runtime conditions |
| 248 | +- **API Extensions**: Extend objects received from external APIs |
| 249 | +- **Testing**: Add test-specific methods to objects during testing |
| 250 | +- **Dynamic UIs**: Add UI-specific properties to data objects |
| 251 | +- **Backwards Compatibility**: Add new features while maintaining old interfaces |
| 252 | + |
| 253 | +## Limitations |
| 254 | + |
| 255 | +- **Performance**: Slightly slower than direct field access |
| 256 | +- **Serialization**: Patched fields may not serialize automatically |
| 257 | +- **Type Information**: Limited compile-time type checking for patched fields |
| 258 | +- **Reflection Dependency**: Relies on Haxe reflection system |
| 259 | + |
| 260 | +## Example: Complete Usage Pattern |
| 261 | + |
| 262 | +```haxe |
| 263 | +// Define base class |
| 264 | +class GameEntity { |
| 265 | + public var name:String; |
| 266 | + public var health:Int; |
| 267 | +
|
| 268 | + public function new(name:String) { |
| 269 | + this.name = name; |
| 270 | + this.health = 100; |
| 271 | + } |
| 272 | +
|
| 273 | + public function takeDamage(amount:Int):Void { |
| 274 | + health -= amount; |
| 275 | + } |
| 276 | +} |
| 277 | +
|
| 278 | +// Create and patch |
| 279 | +var player = new GameEntity("Player"); |
| 280 | +var patchedPlayer:Patched<GameEntity> = player; |
| 281 | +
|
| 282 | +// Add RPG stats extension |
| 283 | +patchedPlayer.addExtension("RPGStats", [ |
| 284 | + { name: "level", type: "Int", isMethod: false, defaultValue: 1 }, |
| 285 | + { name: "experience", type: "Int", isMethod: false, defaultValue: 0 }, |
| 286 | + { name: "levelUp", type: "Void->Void", isMethod: true, |
| 287 | + defaultValue: function():Void { |
| 288 | + var currentLevel = patchedPlayer.getField("level"); |
| 289 | + patchedPlayer.setField("level", currentLevel + 1); |
| 290 | + patchedPlayer.setField("experience", 0); |
| 291 | + trace("Level up! Now level " + patchedPlayer.getField("level")); |
| 292 | + } |
| 293 | + } |
| 294 | +]); |
| 295 | +
|
| 296 | +// Add inventory extension |
| 297 | +patchedPlayer.addExtension("Inventory", [ |
| 298 | + { name: "items", type: "Array<String>", isMethod: false, defaultValue: [] }, |
| 299 | + { name: "addItem", type: "String->Void", isMethod: true, |
| 300 | + defaultValue: function(item:String):Void { |
| 301 | + var items:Array<String> = patchedPlayer.getField("items"); |
| 302 | + items.push(item); |
| 303 | + trace("Added " + item + " to inventory"); |
| 304 | + } |
| 305 | + } |
| 306 | +]); |
| 307 | +
|
| 308 | +// Use both original and patched functionality |
| 309 | +patchedPlayer.takeDamage(25); // Original method |
| 310 | +patchedPlayer.setField("experience", 1000); // Patched field |
| 311 | +patchedPlayer.callMethod("levelUp", []); // Patched method |
| 312 | +patchedPlayer.callMethod("addItem", ["Sword"]); // Another patched method |
| 313 | +
|
| 314 | +trace("Player: " + patchedPlayer.getField("name") + |
| 315 | + ", Level: " + patchedPlayer.getField("level") + |
| 316 | + ", Health: " + patchedPlayer.getField("health")); |
| 317 | +``` |
| 318 | + |
| 319 | +This creates a powerful and flexible system for extending objects at runtime while maintaining compile-time safety and type checking. |
0 commit comments