Skip to content

Commit ebf1cc9

Browse files
committed
eeeeeeeee
1 parent 6f059ee commit ebf1cc9

File tree

5 files changed

+1370
-0
lines changed

5 files changed

+1370
-0
lines changed

Project.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@
273273
<haxeflag name="--macro" value="yutautil.RuntimeTypedef.processTypedef()" />
274274
<haxeflag name="--macro" value="yutautil.VoidFall.randomFailure()" />
275275

276+
<!-- Patched type system - enable field validation on all classes -->
277+
<!-- <haxeflag name="--macro" value="yutautil.Patched.PatchedMacro.init()" /> -->
278+
276279
<haxeflag name="--resource" value="assets/embed/apworld/fridaynightfunkin.apworld@apworld" />
277280
<haxeflag name="--resource" value="example_mods/modTemplate.zip@modTemp" />
278281

docs/Patched_Type_System.md

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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

Comments
 (0)