ludic/changes/state.md
Orkuncakilkaya ce80e64b24 feat(lang): 0.S - components take states in their header; migrate writes them, never inside a name, never outside the programs given, never into a package's state
- component Name (a: mut A, b: B) { ... }: every getter, default, function and event takes the
  header's states before the instance; a member's call to another passes them on; the glue is
  supplied them; the template never sees them; a read-only one is read-only in every member
- ludic migrate state: a component's members' needs go into its header (added to an existing one,
  mut added where now changed); a field read or a member call inside a component is the compiler's,
  so nothing is written inside a name and no ', )' is left; an entry point that declares states
  already gets the rest after them
- a program's module named like a package gets <Name>AppState; a program's own file its own state;
  a friend module's files go by directory; a package's settable var stays state
- it writes only under the programs and directories given (and runtime/ with --runtime), and
  refuses the whole run naming any other file that would have to change
- a name a package already moved into its state is rewritten through it; a read of the runtime's
  var through the runtime function that answers it (gl_w: gl_width())
- a state's instance supplied by the runtime is not a uses reference
- tests: state/component, rejected/state_component_ro, rendering/ui_render3d, migrate component
  and foreign cases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:11:15 +03:00

2.8 KiB

bump: major type: feature No function writes a global: a module's changing data is a state. state Hiker { hips: int = -1 } is a record with exactly one instance, which no code can name; it reaches code as a parameter, h: mut Hiker to change it or h: Hiker to read it, and the runtime supplies it at the entry points (entry (h: mut Hiker), a handler's, a listener's or a lifecycle hook's parameters, a scene's on enter (h: mut Hiker), a test's, a ui block's Hiker.hips, a function value - a system's, a port bind's, a package's callback - an engine system, a namespace method's target and every call into the runtime). A module-level var is refused; a module-level let is immutable all the way down. A read-only state is checked where it is written - an assignment or a push through it or through a local holding part of it, passing it where mut is wanted, a machine over its field - and so is a module-level let. Each test block gets fresh states. A program's type named like one the runtime declares (property PadButton beside the runtime's enum PadButton) is refused instead of failing in clang.

ludic migrate state [file|dir...] [--runtime] [--dry-run] moves programs there: a var nothing writes becomes a let, each module's other vars one state (FishingState, Render3dState, SceneDemoState), every reference fishing_st.name, each function's states - to a fixed point over what it calls - its leading parameters, mut where anything below it writes, and each entry point declares them; the plans of every program given are merged before a file is edited. gpp's 35 mechanic packages, ludic.ui, ludic.render3d, every example, the docs' fences and the runtime were moved with it. The toolchain's own programs build with --globals until they move too.

Outside the runtime, the drawable's size and scale, the screen's framebuffer and the tilemap's tile size are read and set through its functions (gl_width(), gl_set_drawable(w, h), gl_screen_fbo(), gl_pixel_scale(), rt_map_tile_size()), and ludic.ui's render3d backend installs itself through the UiRenderers registry.

A component names the states its members read and change in its header, component Tally (score: mut Score, look: Look) { ... }: every getter, default, function and event takes them before the instance, a member's call to another passes them on, ludic.ui's calls into it are supplied them, and the template never sees them. ludic migrate state writes those headers (and adds to an entry point's or a component's existing list), gives a program's module named like a package a state of its own (FishingAppState), keeps a package's settable vars as state, rewrites a program's references to vars a package already moved into its state, and writes only under the programs and directories it is given - a change needed anywhere else stops the run with the files named.