Skip to content

Commit c1a2272

Browse files
authored
Update generic field mapping documentation (#71)
1 parent 6a1b632 commit c1a2272

2 files changed

Lines changed: 93 additions & 20 deletions

File tree

‎_doc/manual.md‎

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -880,31 +880,75 @@ function foo()
880880

881881
### Compiler-assisted field mapping
882882

883-
For dedicated state classes, the compiler provides two save/load intrinsics that expand into direct field
884-
accesses:
883+
For dedicated state classes, import `MagicFunctions` to use compiler-assisted field iteration and generic
884+
construction. These operations expand to ordinary constructors and direct field accesses; serialization formats,
885+
hashes, and storage remain standard-library concerns.
885886

886887
```wurst
888+
import MagicFunctions
889+
887890
class PlayerState
888891
int level = 1
889892
string name = ""
890893
891894
function save(FieldWriter writer)
892-
__wurst_forFields((fieldName, value) -> writer.write(fieldName, value))
895+
forFields((fieldName, value) -> writer.write(fieldName, value))
893896
894897
function load(FieldReader reader)
895-
__wurst_mapFields((fieldName, value) -> reader.read(fieldName, value))
898+
mapFields((fieldName, value) -> reader.read(fieldName, value))
899+
```
900+
901+
`forFields` invokes the callback once for every accessible, non-static instance field. This includes inherited,
902+
module-injected, readonly, and constant fields. The callback receives the field key and current value and must
903+
produce a statement. `mapFields` assigns each callback result back to its field, so it includes only accessible,
904+
mutable instance fields. Module field keys are qualified when necessary to disambiguate equal names.
905+
906+
Both functions also accept an explicit target. The target is evaluated exactly once:
907+
908+
```wurst
909+
forFields(state, (fieldName, value) -> writer.write(fieldName, value))
910+
mapFields(state, (fieldName, value) -> reader.read(fieldName, value))
911+
```
912+
913+
Leave callback parameter types inferred and overload the reader or writer for every field type used by the state
914+
class. An applicable ordinary visible overload with one of these names is resolved normally and is not treated as
915+
compiler magic.
916+
917+
Use `newInstance<T>()` when a specialized generic function needs to construct its concrete result type:
918+
919+
```wurst
920+
function loadState<T:>(FieldReader reader) returns T
921+
let result = newInstance<T>()
922+
mapFields(result, (fieldName, oldValue) -> reader.read(fieldName, oldValue))
923+
return result
896924
```
897925

898-
`__wurst_forFields` invokes the callback once for every non-static instance field. The callback receives the
899-
field name and current value and must produce a statement. `__wurst_mapFields` invokes a reader callback and
900-
assigns its result back to each field. Leave the two callback parameter types inferred; overload the reader or
901-
writer for the field types used by the state class.
926+
At each concrete call such as `loadState<PlayerState>(reader)`, the compiler specializes the required path and
927+
lowers `newInstance<PlayerState>()` to its normal zero-argument constructor. `T` must resolve to a concrete,
928+
non-abstract class with an accessible zero-argument constructor. Interfaces, handles, primitives, tuples,
929+
unresolved type parameters, and classes without a usable constructor are rejected.
930+
931+
Keep a generic loader in the free-function form shown above. On Lua, a method cannot currently combine type
932+
parameters from its generic owning class with additional type parameters declared by the method itself.
933+
Likewise, do not call `newInstance<T>()` from a generic class constructor. Construct the state in the loader and
934+
initialize nested state explicitly afterward.
935+
936+
On Lua, do not invoke a generic-construction method directly on a freshly constructed generic receiver. Prefer the
937+
free loader above, or store the receiver in a typed local first. Multi-parameter generic-interface dispatch is also
938+
outside this loader contract; use one construction type parameter. `newInstance<T>()` is for runtime Jass/Lua
939+
construction and is not supported inside `compiletime(...)` expressions. Field mapping also does not support
940+
nested modules whose sibling submodules declare fields with the same name; use direct fields, inheritance, or
941+
unique shallow module field names for dedicated state classes.
902942

903943
These are compile-time transformations, not runtime reflection, and generate equivalent direct accesses in both
904-
Jass and Lua. Keep serializable state in a small class with mutable instance fields and keep the persistence
905-
codec separate from the rest of the game logic. See the [Save and Load tutorial](/tutorials/saveload.html) for
944+
Jass and Lua. They generate no runtime registry, type-name lookup, or reflection metadata. Keep serializable state
945+
in small, dedicated classes, avoid unsupported field kinds such as static fields, and keep persistence codecs and
946+
format migration separate from the state model. See the [Save and Load tutorial](/tutorials/saveload.html) for
906947
integration with Warcraft III's file API.
907948

949+
Identifiers beginning with `__wurst` are reserved for compiler-generated internals and must not be declared by
950+
user code.
951+
908952
### Array Members
909953

910954
Wurstscript supports sized arrays as classmembers by translating it to SIZE times arrays and then resolve the array in a get/set function via binary search.

‎_tutorials/saveload.md‎

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,32 +64,61 @@ The `serialize()` function returns a `ChunkedString`, which then can be passed t
6464

6565
## Compiler-assisted field mapping
6666

67-
For small, dedicated state classes, Wurst can generate the repetitive field mapping for you. Use the
68-
compiler intrinsics `__wurst_forFields` when writing fields and `__wurst_mapFields` when reading them:
67+
For small, dedicated state classes, Wurst can generate the repetitive field mapping for you. Import
68+
`MagicFunctions`, then use `forFields` when writing fields and `mapFields` when reading them:
6969

7070
```wurst
71+
import MagicFunctions
72+
7173
class PlayerState
7274
int level = 1
7375
string name = ""
7476
7577
function save(FieldWriter writer)
76-
__wurst_forFields((fieldName, value) -> writer.write(fieldName, value))
78+
forFields((fieldName, value) -> writer.write(fieldName, value))
7779
7880
function load(FieldReader reader)
79-
__wurst_mapFields((fieldName, value) -> reader.read(fieldName, value))
81+
mapFields((fieldName, value) -> reader.read(fieldName, value))
8082
```
8183

8284
The callback receives the field name as a `string` and the current field value. The compiler expands these
8385
calls into ordinary direct field accesses, so there is no runtime reflection or metadata lookup. The same
8486
source works for both Jass and Lua.
8587

86-
`__wurst_forFields` is for statement callbacks. `__wurst_mapFields` uses the callback result to assign each
87-
field, so the reader should return the value to store. Leave both callback parameter types inferred and use
88-
the reader/writer overload matching each field type. Static fields are not included.
88+
`forFields` is for statement callbacks. It includes accessible non-static fields, including inherited,
89+
module-injected, readonly, and constant state. `mapFields` uses the callback result to assign each field, so the
90+
reader should return the value to store; readonly and constant fields are therefore excluded from mapping. Leave
91+
both callback parameter types inferred and use a reader/writer overload for each field type. Module field keys are
92+
qualified when equal names need disambiguation.
93+
94+
The explicit-target forms work outside the state class and evaluate the target exactly once:
95+
96+
```wurst
97+
forFields(state, (fieldName, value) -> writer.write(fieldName, value))
98+
mapFields(state, (fieldName, value) -> reader.read(fieldName, value))
99+
```
100+
101+
For a generic load wrapper, `newInstance<T>()` constructs the specialized concrete class through its normal
102+
accessible zero-argument constructor:
103+
104+
```wurst
105+
function loadState<T:>(FieldReader reader) returns T
106+
let state = newInstance<T>()
107+
mapFields(state, (fieldName, oldValue) -> reader.read(fieldName, oldValue))
108+
return state
109+
```
89110

90-
The `__wurst_` names are compiler intrinsics; ordinary user functions named `forFields` or `mapFields` are
91-
unaffected. Keep these state classes focused on their serializable data (normally direct mutable instance
92-
fields) and keep the persistence codec separate from the game logic.
111+
This works for Jass and Lua without runtime reflection, a type registry, or a type-id switch. `T` must resolve to a
112+
concrete, non-abstract class with an accessible zero-argument constructor. Keep state classes focused on data and
113+
keep the persistence codec, schema versioning, validation, and migrations separate from construction and field
114+
mapping. Applicable ordinary visible overloads named `forFields`, `mapFields`, or `newInstance` still resolve
115+
normally. Keep the generic loader as a free function: on Lua, a method cannot currently combine type parameters
116+
from its generic owning class with additional type parameters declared by the method itself. Do not call
117+
`newInstance<T>()` from a generic class constructor; construct the state in the loader and initialize nested state
118+
explicitly afterward. Avoid calling generic-construction methods directly on freshly constructed generic receivers
119+
on Lua, and keep the loader to one construction type parameter rather than multi-parameter generic-interface
120+
dispatch. `newInstance<T>()` is not supported inside `compiletime(...)` expressions. Dedicated state classes should
121+
also avoid nested modules with sibling fields sharing the same name; prefer direct fields or ordinary inheritance.
93122

94123

95124
## Usage

0 commit comments

Comments
 (0)