plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
23627d9
— Peter John
2026-11-02T18:18:04+05:30
docs(plans): scene z-index, sortChildren and culling review plan
docs/plans/2026-11-02-scene-zindex-culling.review.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# std/Scene: Scene2D z-index, child sorting and culling
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
`std/Scene` already draws children in list order, as libGDX Scene2D does. This adds Scene2D's API for changing that order:
|
|
5
|
+
- z-index as the child's position in its parent's list;
|
|
6
|
+
- `toFront`/`toBack`;
|
|
7
|
+
- an in-place, stable `sortChildren`, for y-sorting a world layer every frame;
|
|
8
|
+
- Scene2D-style culling: a group with a culling area skips children whose bounds lie outside it.
|
|
9
|
+
|
|
10
|
+
These are the pieces the KaM port needs to draw a large y-sorted map through the scene graph. Existing scenes don't change.
|
|
11
|
+
|
|
12
|
+
## What changes for Plum code
|
|
13
|
+
```plum
|
|
14
|
+
n.zIndex() # index among its siblings, -1 without a parent
|
|
15
|
+
n.setZIndex(0) # move within the parent's child list (clamped to 0..length-1)
|
|
16
|
+
n.toFront() # last child: drawn on top, hit first
|
|
17
|
+
n.toBack() # first child: drawn first
|
|
18
|
+
|
|
19
|
+
world.sortChildren(|a, b| a.y < b.y) # stable, in place, no allocation; cheap on nearly-sorted lists
|
|
20
|
+
|
|
21
|
+
world.setCullingArea(cam.x, cam.y, viewW, viewH) # in world's own coordinates
|
|
22
|
+
world.clearCullingArea()
|
|
23
|
+
```
|
|
24
|
+
- These follow Scene2D's semantics and names (`getZIndex`/`setZIndex`/`toFront`/`toBack`, `Group.setCullingArea`), with Plum's naming conventions.
|
|
25
|
+
- **Z-index is the list position:** there is no separate z field. Draw order is list order, and hit-testing goes in reverse list order, as it does today.
|
|
26
|
+
- **`sortChildren(before)`:**
|
|
27
|
+
- it reorders the existing child list in place;
|
|
28
|
+
- it is stable (equal keys keep their order, so ties don't flicker);
|
|
29
|
+
- it is O(n) when the list is nearly sorted (the per-frame y-sort case) and O(n log n) in the worst case. A natural merge sort or binary-insertion runs with merging, using a reusable scratch buffer kept on the node, so steady-state frames allocate nothing.
|
|
30
|
+
- **Culling, as in Scene2D:**
|
|
31
|
+
- the culling area is a rectangle in the group's own coordinates;
|
|
32
|
+
- a child is skipped from drawing and from hit-testing when its own box (x, y, width, height, scaled, rotation ignored) doesn't overlap the area;
|
|
33
|
+
- culling applies one level down (the group's direct children);
|
|
34
|
+
- a culled child's whole subtree is skipped.
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
- `Node` gains `cull: Bool, cullX/cullY/cullW/cullH: Float` and a private scratch `List[Node]` for sorting.
|
|
38
|
+
- `setZIndex`/`toFront`/`toBack` use `parent` (already on `Node`): remove the node from the parent's children and insert it at the new index.
|
|
39
|
+
- `drawNode`, `collect` (flatten) and `hitNode` check the parent's culling area before descending into a child.
|
|
40
|
+
|
|
41
|
+
## Work, in order
|
|
42
|
+
1. Z-index API + `sortChildren`, plus pure `test` blocks:
|
|
43
|
+
- order after setZIndex, toFront and toBack;
|
|
44
|
+
- a stable sort with ties;
|
|
45
|
+
- y-sort of 5,000 random nodes is correct;
|
|
46
|
+
- a nearly-sorted re-sort takes ≈ n comparisons, counted through the comparator;
|
|
47
|
+
- no allocation in steady state, checked by reusing the node's scratch buffer identity.
|
|
48
|
+
2. Culling, plus `test` blocks:
|
|
49
|
+
- flatten skips children outside the area;
|
|
50
|
+
- hit doesn't return culled children;
|
|
51
|
+
- a scaled child near the edge is kept;
|
|
52
|
+
- clearing the area restores them.
|
|
53
|
+
3. `plum-examples/scene.plum`: a y-sorted layer of moving sprites using `sortChildren` and `setCullingArea` from the camera view, plus `toFront` on the clicked node.
|
|
54
|
+
4. Benchmark: `plum-game/web-test/fixtures/stress_scene.plum` variant with a 20,000-node world, of which ~2,000 are on screen, using a per-frame y-sort and culling. Record frame time with and without culling in `BENCHMARK.md`.
|
|
55
|
+
5. README `std/Scene` section: z-index, sortChildren, culling, and the KaM-style layer recipe (terrain / world y-sorted / effects / ui).
|
|
56
|
+
|
|
57
|
+
## How we will know it works
|
|
58
|
+
- The `std/Scene` tests cover every rule above. `plum test plum-std plum-examples plum-game/web-test/fixtures` passes, along with `cargo test --workspace` and the plum-game and web-test suites.
|
|
59
|
+
- The browser check passes for scene.plum and the new fixture; the screenshots look right.
|
|
60
|
+
- In the benchmark, culling makes frame time scale with what's on screen, not with total nodes; with culling on, the 20k-node fixture stays close to the 5k-node fixture.
|
|
61
|
+
|
|
62
|
+
## Decisions (settled)
|
|
63
|
+
- Scene2D semantics and names (user ruling).
|
|
64
|
+
- Z-index = list position, not a stored number.
|
|
65
|
+
- The culling area is in the group's own coordinates and ignores rotation, as in Scene2D.
|
|
66
|
+
|
|
67
|
+
## Not in scope
|
|
68
|
+
- Scene2D actions (sequences, tweens), layout widgets/tables, and event listeners/bubbling.
|
|
69
|
+
- Spatial indexing (a `Grid` in game code stays the tool for gameplay queries).
|