diff options
| author | Gene Pasquet <dev@etenil.net> | 2026-04-08 01:57:20 +0100 |
|---|---|---|
| committer | Gene Pasquet <dev@etenil.net> | 2026-04-08 01:57:20 +0100 |
| commit | 9e8b75f9949259ef01942cd3717b79b044efddf7 (patch) | |
| tree | c6b71291ade57f0560a9bbf0db9f5b66bab65cb3 /docs | |
| parent | 84840ede6646ed793b61cdd889d3f57ab05e9311 (diff) | |
Refactor update pipelines
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/api.org | 42 | ||||
| -rw-r--r-- | docs/entities.org | 18 | ||||
| -rw-r--r-- | docs/physics.org | 38 | ||||
| -rw-r--r-- | docs/tweens.org | 2 |
4 files changed, 59 insertions, 41 deletions
diff --git a/docs/api.org b/docs/api.org index 15c6e54..e60ef7d 100644 --- a/docs/api.org +++ b/docs/api.org @@ -255,10 +255,10 @@ Returns a new camera centered on an entity, clamping to stay within world bounds Appends an entity to the scene's entity list. Returns a new scene; the original is not modified. -** ~scene-update-entities~ +** ~scene-map-entities~ #+begin_src scheme -(scene-update-entities scene proc1 proc2 ...) +(scene-map-entities scene proc1 proc2 ...) #+end_src Applies each procedure in sequence to all entities in the scene. Each procedure takes a single entity and returns a modified entity. Returns a new scene with the updated entity list; the original is not modified. @@ -272,17 +272,31 @@ Example: (define (apply-gravity entity) (entity-set entity #:vy (+ 1 (entity-ref entity #:vy 0)))) -(scene-update-entities scene increment-x apply-gravity) +(scene-map-entities scene increment-x apply-gravity) ; Each entity is passed through increment-x, then through apply-gravity #+end_src -** ~scene-sync-groups~ +** ~scene-transform-entities~ #+begin_src scheme -(scene-sync-groups scene) +(scene-transform-entities scene proc) #+end_src -For every entity with ~#:group-id~ that is not an origin (~#:group-origin?~ is false), sets ~#:x~ and ~#:y~ to the corresponding origin’s position plus that entity’s ~#:group-local-x~ and ~#:group-local-y~. Origins are read from ~scene-entities~, so after a tween or other motion that returns a *new* origin plist, replace that origin in the scene’s list (match on ~#:group-id~ / ~#:group-origin?~) before calling ~scene-sync-groups~. Call after updating origin positions and before per-entity physics so platforms and collisions see a consistent pose. Returns a new scene; the original is not modified. +Applies ~proc~ to the scene’s full entity list. ~proc~ must have signature ~(entities → entities)~: it receives the current list and returns a new list. Returns a new scene with that entity list; the original is not modified. Use this to run whole-list steps such as ~sync-groups~ or ~resolve-entity-collisions~ after ~scene-map-entities~ (or in any order your game needs). + +** ~sync-groups~ + +#+begin_src scheme +(sync-groups entities) +#+end_src + +For every entity with ~#:group-id~ that is not an origin (~#:group-origin?~ is false), sets ~#:x~ and ~#:y~ to the corresponding origin’s position plus that entity’s ~#:group-local-x~ and ~#:group-local-y~. Origins are read from the **entity list** argument (typically ~(scene-entities scene)~ when you compose with ~scene-transform-entities~), so after a tween or other motion that returns a *new* origin plist, replace that origin in the list (match on ~#:group-id~ / ~#:group-origin?~) before calling ~sync-groups~. Call after updating origin positions and before per-entity physics so platforms and collisions see a consistent pose. Returns a new entity list. + +Typical usage: + +#+begin_src scheme +(scene-transform-entities scene sync-groups) +#+end_src ** ~scene-filter-entities~ @@ -411,10 +425,14 @@ Returns true if ~step-symbol~ appears in the entity’s ~#:skip-pipelines~ list ** ~define-pipeline~ #+begin_src scheme -(define-pipeline (procedure-name skip-symbol) (entity-formal extra-formal ...) body ...) +(define-pipeline (procedure-name skip-symbol) (entity-formal extra-formal ...) + guard: guard-expr + body ...) #+end_src -Syntax for authors of per-entity pipeline steps: expands to a ~define~ that returns the **first** formal (the entity) unchanged when ~skip-symbol~ is listed in ~#:skip-pipelines~; otherwise runs ~body ...~ inside ~(let () ...)~. Used throughout ~downstroke-physics~; other modules can use it for consistent skip behavior. The procedure name and skip symbol differ when needed (e.g. ~detect-on-solid~ vs ~on-solid~). +The ~guard:~ clause is optional. When present, ~guard-expr~ is evaluated first; if it is false, the entity is returned unchanged and ~body ...~ does not run. When absent, the body applies to all entities (subject only to the skip-symbol check below). + +Syntax for authors of per-entity pipeline steps: expands to a ~define~ that returns the **first** formal (the entity) unchanged when ~skip-symbol~ is listed in ~#:skip-pipelines~; otherwise, if a guard is present and fails, returns the entity unchanged; otherwise runs ~body ...~ inside ~(let () ...)~. Used throughout ~downstroke-physics~; other modules can use it for consistent skip behavior. The procedure name and skip symbol differ when needed (e.g. ~detect-on-solid~ vs ~on-solid~). ** Shared Entity Keys @@ -547,14 +565,12 @@ If the jump button is pressed and the entity is on ground, sets ~#:ay~ to ~(- #: Detects and resolves AABB collisions between all pairs of entities with ~#:solid?~ true. Pushes overlapping entities apart along the axis of minimum penetration and sets their velocities in the push direction. Returns a new entity list. -** ~scene-resolve-collisions~ +There is no scene-level wrapper; apply ~resolve-entity-collisions~ to the entity list via ~scene-transform-entities~: #+begin_src scheme -(scene-resolve-collisions scene) +(scene-transform-entities scene resolve-entity-collisions) #+end_src -Applies ~resolve-entity-collisions~ to the scene's entity list. Returns the modified scene. - ** Physics Constants - ~*gravity*~ = 1 (pixels per frame per frame) @@ -1333,7 +1349,7 @@ Looks up a prefab by type symbol in the registry and returns a fresh entity plis (instantiate-group-prefab registry type origin-x origin-y) #+end_src -Looks up a *group prefab* by type symbol and returns a list ~(origin member ...)~: one origin entity plus one entity per part. Optional group-level flags ~#:pose-only-origin?~ and ~#:static-parts?~ select origin/part profiles (see ~docs/entities.org~); defaults are ~#f~ (physics-driving origin, non-static parts). Each instance receives a fresh gensym ~#:group-id~ shared by the origin and all members. Returns ~#f~ if the type is not in ~group-prefabs~. After moving origins (tween and/or physics), ensure updated origins are stored in the scene’s entity list, then call ~scene-sync-groups~ so member ~#:x~ / ~#:y~ match ~origin + #:group-local-x/y~. +Looks up a *group prefab* by type symbol and returns a list ~(origin member ...)~: one origin entity plus one entity per part. Optional group-level flags ~#:pose-only-origin?~ and ~#:static-parts?~ select origin/part profiles (see ~docs/entities.org~); defaults are ~#f~ (physics-driving origin, non-static parts). Each instance receives a fresh gensym ~#:group-id~ shared by the origin and all members. Returns ~#f~ if the type is not in ~group-prefabs~. After moving origins (tween and/or physics), ensure updated origins are stored in the scene’s entity list, then ~(scene-transform-entities scene sync-groups)~ so member ~#:x~ / ~#:y~ match ~origin + #:group-local-x/y~. ** ~tilemap-objects->entities~ diff --git a/docs/entities.org b/docs/entities.org index 3766522..9f8e9ee 100644 --- a/docs/entities.org +++ b/docs/entities.org @@ -20,7 +20,7 @@ This minimal approach keeps the engine lean: your game defines whatever keys it * Pipeline skips (~#:skip-pipelines~) -The optional key ~#:skip-pipelines~ holds a list of **symbols** naming frame pipeline steps that should be skipped for that entity. The physics module defines the built-in step names (see ~docs/physics.org~). The predicate ~entity-skips-pipeline?~ and the syntax ~define-pipeline~ live in ~downstroke-entity~ so any subsystem (physics now; rendering or animation later if you extend the engine) can use the same mechanism without a separate “core pipeline” module. +The optional key ~#:skip-pipelines~ holds a list of **symbols** naming frame pipeline steps that should be skipped for that entity. The physics module defines the built-in step names (see ~docs/physics.org~). The predicate ~entity-skips-pipeline?~ and the syntax ~define-pipeline~ (with optional ~guard:~ clause per step) live in ~downstroke-entity~ so any subsystem (physics now; rendering or animation later if you extend the engine) can use the same mechanism without a separate “core pipeline” module. * Creating Entities @@ -126,19 +126,19 @@ The engine recognizes these standard keys. Use them to integrate with the physic | ~#:anim-tick~ | integer | Tick counter for frame timing (0 to ~#:duration - 1~). Incremented by ~animate-entity~; resets when frame advances. | | ~#:group-id~ | symbol | Shared id for one rigid assembly (from ~instantiate-group-prefab~). All parts and the origin share the same symbol. | | ~#:group-origin?~ | boolean | When ~#t~, this entity is the assembly’s pose origin; world ~#:x~ / ~#:y~ drive the group. Members should not set this. | -| ~#:group-local-x~, ~#:group-local-y~ | number | Offset from the origin’s top-left corner; members’ world position is origin + local (updated by ~scene-sync-groups~). | +| ~#:group-local-x~, ~#:group-local-y~ | number | Offset from the origin’s top-left corner; members’ world position is origin + local (updated by ~sync-groups~ on the entity list, e.g. ~(scene-transform-entities scene sync-groups)~). | | ~#:skip-render~ | boolean | When ~#t~, ~render-scene!~ skips drawing this entity (used for invisible origins). | * Entity groups (prefab assemblies) A **group prefab** describes one *origin* entity plus several *parts* with local offsets. Data lives in the optional ~group-prefabs~ section of the prefab file (alongside ~mixins~ and ~prefabs~). Each group entry has the shape ~(name #:type-members SYMBOL #:parts (part ...) ...)~ with two optional flags: -- ~#:pose-only-origin?~ — when ~#t~ (typical for tweened platforms), the origin is invisible, does not run physics pipelines, and is driven by tweens or scripts. When ~#f~ (default), the origin uses a small *physics-driving* profile (~#:gravity? #t~, no ~#:skip-pipelines~): integrate the origin like a mover, then call ~scene-sync-groups~ so parts stay glued as a rigid body. For that case, set ~#:origin-width~ and ~#:origin-height~ to the full assembly size (same box as the combined parts); otherwise the origin stays 0×0 and tile collision only sees a point at the reference corner, which can leave the raft overlapping solid floor tiles. +- ~#:pose-only-origin?~ — when ~#t~ (typical for tweened platforms), the origin is invisible, does not run physics pipelines, and is driven by tweens or scripts. When ~#f~ (default), the origin uses a small *physics-driving* profile (~#:gravity? #t~, no ~#:skip-pipelines~): integrate the origin like a mover, then ~(scene-transform-entities scene sync-groups)~ so parts stay glued as a rigid body. For that case, set ~#:origin-width~ and ~#:origin-height~ to the full assembly size (same box as the combined parts); otherwise the origin stays 0×0 and tile collision only sees a point at the reference corner, which can leave the raft overlapping solid floor tiles. - ~#:static-parts?~ — when ~#t~, each part gets static rigid-body defaults (no gravity on parts; pose comes from the origin). When ~#f~ (default), parts only have what you put in each part plist. Each ~part~ is a plist using ~#:local-x~ / ~#:local-y~ (or ~#:group-local-x~ / ~#:group-local-y~) and the usual ~#:width~, ~#:height~, ~#:tile-id~, physics keys, etc. -Use ~(instantiate-group-prefab registry 'name origin-x origin-y)~ from ~downstroke-prefabs~ to obtain ~(origin member ...)~. Append all of them to the scene. After moving origins (tweens and/or physics), ensure updated origins are in ~scene-entities~, then call ~(scene-sync-groups scene)~ so every part’s ~#:x~ / ~#:y~ matches the origin plus local offsets (see ~docs/api.org~ for ordering). +Use ~(instantiate-group-prefab registry 'name origin-x origin-y)~ from ~downstroke-prefabs~ to obtain ~(origin member ...)~. Append all of them to the scene. After moving origins (tweens and/or physics), ensure updated origins are in ~scene-entities~, then ~(scene-transform-entities scene sync-groups)~ so every part’s ~#:x~ / ~#:y~ matches the origin plus local offsets (see ~docs/api.org~ for ordering). * Entities in Scenes @@ -168,13 +168,13 @@ Returns a new scene with the entity appended to the entity list. (scene-add-entity scene new-enemy) #+end_src -** ~scene-update-entities scene proc1 proc2 ...~ +** ~scene-map-entities scene proc1 proc2 ...~ Maps each procedure over the scene's entities, applying them in sequence. Each proc must be a function of one entity, returning a new entity. #+begin_src scheme ;; Apply physics pipeline to all entities: -(scene-update-entities scene +(scene-map-entities scene apply-gravity apply-velocity-x apply-velocity-y) @@ -184,9 +184,9 @@ The result is equivalent to: #+begin_src scheme (chain scene - (scene-update-entities _ apply-gravity) - (scene-update-entities _ apply-velocity-x) - (scene-update-entities _ apply-velocity-y)) + (scene-map-entities _ apply-gravity) + (scene-map-entities _ apply-velocity-x) + (scene-map-entities _ apply-velocity-y)) #+end_src ** ~scene-filter-entities scene pred~ diff --git a/docs/physics.org b/docs/physics.org index 8401a38..e5b3749 100644 --- a/docs/physics.org +++ b/docs/physics.org @@ -76,7 +76,7 @@ Helper: ~(entity-skips-pipeline? entity step-symbol)~ (from ~downstroke-entity~) ** ~define-pipeline~ (~downstroke-entity~) -Physics steps are defined with ~(define-pipeline (procedure-name skip-symbol) (formals ...) body ...)~ from the entity module. The first formal must be the entity. The procedure name and skip symbol are separate (e.g. ~detect-on-solid~ vs ~on-solid~). ~apply-velocity~ is still written by hand because it consults ~velocity-x~ and ~velocity-y~ independently. +Physics steps are defined with ~(define-pipeline (procedure-name skip-symbol) (formals ...) body ...)~ from the entity module, optionally with ~guard: expr~ before ~body ...~: when the guard is false, the entity is returned unchanged before the body runs. The first formal must be the entity. The procedure name and skip symbol are separate (e.g. ~detect-on-solid~ vs ~on-solid~). ~apply-velocity~ is still written by hand because it consults ~velocity-x~ and ~velocity-y~ independently. The renderer and other subsystems do **not** use ~#:skip-pipelines~ today; they run after your ~update:~ hook. If you add render-phase or animation-phase skips later, reuse the same plist key and helpers from ~downstroke-entity~ and document the new symbols alongside physics. @@ -225,13 +225,15 @@ Entities without =#:solid?= or with =#:solid? #f= are skipped. Returns a new ent This is relatively expensive: O(n²) for n entities. Use only when entity count is low (< 100) or for game objects where push-apart is desired. -** scene-resolve-collisions +** Using =resolve-entity-collisions= on a scene + +Apply the pure list function via =scene-transform-entities= (from =downstroke-world=): #+begin_src scheme -(scene-resolve-collisions scene) +(scene-transform-entities scene resolve-entity-collisions) #+end_src -**Description**: Convenience wrapper. Extracts all entities from the scene, passes them to =resolve-entity-collisions=, and updates the scene in place. Modifies the scene. +Returns a new scene with the updated entity list; the original scene is not modified. ** aabb-overlap? @@ -397,27 +399,27 @@ Multiple entities falling and colliding with each other: update: (lambda (game dt) (let* ((scene (game-scene game)) (tm (scene-tilemap scene))) - ;; Apply physics to all entities in one pass - (scene-update-entities scene - apply-gravity - apply-velocity-x - (lambda (e) (resolve-tile-collisions-x e tm)) - apply-velocity-y - (lambda (e) (resolve-tile-collisions-y e tm)) - (lambda (e) (detect-on-solid e tm))) - ;; Then resolve entity-entity collisions - (scene-resolve-collisions scene))) + ;; Apply physics to all entities in one pass, then resolve entity-entity collisions + (scene-transform-entities + (scene-map-entities scene + apply-gravity + apply-velocity-x + (lambda (e) (resolve-tile-collisions-x e tm)) + apply-velocity-y + (lambda (e) (resolve-tile-collisions-y e tm)) + (lambda (e) (detect-on-solid e tm))) + resolve-entity-collisions))) #+end_src ** step-by-step -1. **scene-update-entities**: applies each step to all entities in order +1. **scene-map-entities**: applies each step to all entities in order - =apply-gravity= (all entities fall) - =apply-velocity-x=, =resolve-tile-collisions-x= (move and collide on x-axis) - =apply-velocity-y=, =resolve-tile-collisions-y= (move and collide on y-axis) - =detect-on-solid= (set #:on-ground? for next frame) -2. **scene-resolve-collisions**: after all entities are moved and collided with tiles, resolve entity-entity overlaps (boxes pushing apart) +2. **scene-transform-entities** with **resolve-entity-collisions**: after all entities are moved and collided with tiles, resolve entity-entity overlaps (boxes pushing apart) This pattern is efficient for sandbox simulations: apply the same pipeline to all entities, then resolve inter-entity collisions once. @@ -429,7 +431,7 @@ Notice that =resolve-tile-collisions-x= needs the tilemap argument, so it's wrap (lambda (e) (resolve-tile-collisions-x e tm)) #+end_src -Same for other functions that take tilemap. The =scene-update-entities= macro applies each function to all entities, so you wrap single-argument functions in a lambda to capture the tilemap. +Same for other functions that take tilemap. The =scene-map-entities= macro applies each function to all entities, so you wrap single-argument functions in a lambda to capture the tilemap. * Common Patterns @@ -514,7 +516,7 @@ For large games, consider spatial partitioning (grid, quadtree) to cull entity p ** Entities Get Stuck Overlapping -- Use =scene-resolve-collisions= after all physics steps +- Use =(scene-transform-entities scene resolve-entity-collisions)= after all physics steps - Verify both entities have =#:solid? #t= - Reduce =*gravity*= or max velocity if entities are moving too fast (can cause multi-frame overlap) diff --git a/docs/tweens.org b/docs/tweens.org index 213b3ee..c278814 100644 --- a/docs/tweens.org +++ b/docs/tweens.org @@ -71,7 +71,7 @@ Auto-advances ~#:tween~ on an entity. If the entity has no ~#:tween~ key, return This is the recommended way to run tweens in most games. Attach a tween to an entity and include ~step-tweens~ in your per-entity pipeline: #+begin_src scheme -(scene-update-entities scene +(scene-map-entities scene (lambda (e) (step-tweens e dt))) #+end_src |
