docs(pkg): document @Namespace / @EngineSystem / @System + package hooks (#62)
Per-annotation pages for the three package-registration annotations (with inventory entries), a "Package-declarable namespaces and engine systems" section in docs/PACKAGES.md, and a changeset. All doc gates green (check-docs 675 fences, docs-check 726 symbols). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7cbd5d175c
commit
6c6dfae235
6 changed files with 119 additions and 1 deletions
33
docs/language/annotations/annot-enginesystem.md
Normal file
33
docs/language/annotations/annot-enginesystem.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
id: annot-enginesystem
|
||||
name: @EngineSystem
|
||||
category: annotations
|
||||
kind: annotation
|
||||
tokens: @EngineSystem
|
||||
sig: "@EngineSystem(Component, Phase) function esys_name() { … }"
|
||||
tip: Register a package's engine-owned system so the frame loop runs it each phase.
|
||||
order: 60
|
||||
---
|
||||
|
||||
<code>@EngineSystem(Component, Phase)</code> marks a function as an engine-owned system: the compiler adds it to the engine-system registry, and the frame loop calls it every <code>Phase</code> — after the game's own handlers — whenever the named <code>Component</code> is present in the program. It is the package-declarable form of the built-in engine systems (the ones that advance <code>SpriteAnim</code>/<code>Motion</code>/<code>Light2D</code>), so a third-party controller ships a system the same way the standard library does, with no compiler edit. The function reads and writes components by name through the reflection ABI, so it stays layout-independent and additive: a build that never declares the component compiles byte-for-byte the same. Phases are <code>Input</code>, <code>FixedUpdate</code>, <code>Update</code>, <code>LateUpdate</code> and <code>Render</code>.
|
||||
|
||||
```ludic
|
||||
program EngineSystemDemo {
|
||||
property Score { value: int = 0 }
|
||||
model Player { Score }
|
||||
|
||||
@EngineSystem(Score, Update) function esys_score() -> void { # runs every Update
|
||||
let P = world_prop_id("Score")
|
||||
let e = world_query_next(P, 0)
|
||||
if e >= 0 { world_set(e, P, 0, world_get(e, P, 0) + 1) } # advance by name
|
||||
}
|
||||
|
||||
@OnSpawn(Player) handler Init { }
|
||||
handler Boot phase Start { let e = world_spawn(world_model_id("Player")) }
|
||||
handler Run phase Update {
|
||||
let P = world_prop_id("Score")
|
||||
print(world_get(world_query_next(P, 0), P, 0)) # grows each frame
|
||||
quit()
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/annotations/annot-namespace.md
Normal file
24
docs/language/annotations/annot-namespace.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: annot-namespace
|
||||
name: @Namespace
|
||||
category: annotations
|
||||
kind: annotation
|
||||
tokens: @Namespace
|
||||
sig: "@Namespace(Name) function name_method(…) -> T { … }"
|
||||
tip: Let a package provide a Name.method(…) namespace that dispatches to name_method.
|
||||
order: 61
|
||||
---
|
||||
|
||||
<code>@Namespace(Name)</code> registers <code>Name</code> as a namespace a package provides, so a caller can write <code>Name.method(…)</code> and the compiler dispatches it to the bare function <code>name_method(…)</code> (the namespace lowercased, an underscore, then the method). It is the package-declarable form of the built-in <code>Foo.*</code> stdlib namespaces (<code>Regex.*</code>, <code>Grid.*</code>, …): a third-party package ships the same call shape without editing the compiler. The alias goes through the same generic call path the core namespaces use and preserves the target function's return type; it only applies after every built-in namespace, so it never shadows a core one. Mark each function that backs a method (the registration is idempotent — one mark is enough to open the namespace, but marking each provider documents the surface).
|
||||
|
||||
```ludic
|
||||
program NamespaceDemo {
|
||||
@Namespace(Coach) function coach_bonus() -> int { return 99 }
|
||||
@Namespace(Coach) function coach_double(n: int) -> int { return n + n }
|
||||
|
||||
entry {
|
||||
print(Coach.bonus()) # 99 — dispatches to coach_bonus
|
||||
print(Coach.double(21)) # 42 — dispatches to coach_double
|
||||
}
|
||||
}
|
||||
```
|
||||
24
docs/language/annotations/annot-system.md
Normal file
24
docs/language/annotations/annot-system.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
id: annot-system
|
||||
name: @System
|
||||
category: annotations
|
||||
kind: annotation
|
||||
tokens: @System
|
||||
sig: "@System(Phase) function name() { … }"
|
||||
tip: Register a prebuilt binary module's system with the host at load, per phase.
|
||||
order: 62
|
||||
---
|
||||
|
||||
<code>@System(Phase)</code> marks a function in a prebuilt binary module (one compiled with <code>--emit-module</code>) as a runtime-registered system. The module carries a load-time constructor that hands each such function to the host through <code>ludic_register_system</code>, and the host's frame loop calls it every <code>Phase</code> after its own handlers — the runtime, escape-hatch counterpart of the compile-time <code>@EngineSystem</code>. The compiler supplies the function's address (Ludic source cannot take one). This is how a closed-source or other-language module contributes systems over the reflection C-ABI; source packages that compile into the consumer should prefer <code>@EngineSystem</code>. Phases are <code>Input</code>, <code>FixedUpdate</code>, <code>Update</code>, <code>LateUpdate</code> and <code>Render</code>.
|
||||
|
||||
```ludic
|
||||
program ManaModule {
|
||||
function module_init() -> void { world_register_prop("Mana", 2) } # register a component at load
|
||||
|
||||
@System(Update) function mana_regen() -> void { # runs every Update in the host
|
||||
let M = world_prop_id("Mana")
|
||||
let e = world_query_next(M, 0)
|
||||
if e >= 0 { world_set(e, M, 0, world_get(e, M, 0) + 1) }
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue