plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


e0be96f — Peter John 2026-11-02T18:24:45+05:30
scene demo: y-sorted, culled crowd of walkers with toFront on click
Files changed (1) hide show
  1. plum-examples/scene.plum +61 -2
plum-examples/scene.plum CHANGED
@@ -3,7 +3,10 @@
3
3
  # is a group whose rotation carries everything inside it, so a moon only ever
4
4
  # says "I sit 40 units from my planet" and the maths composes the rest. Click a
5
5
  # body to select it (a halo and its name), click again or the background to clear;
6
+ # Space pauses. A crowd of small walkers crosses the bottom of the screen: they are
7
+ # y-sorted every frame with `sortChildren` (lower ones draw in front), culled
8
+ # against the camera view with `setCullingArea`, and a clicked walker is brought
6
- # Space pauses.
9
+ # to the front with `toFront`.
7
10
  #
8
11
  # The scene is built and queried by plain functions that never call the host, so
9
12
  # the `test` blocks below run under `plum test`; only `init`, `update` and `draw`
@@ -29,6 +32,9 @@ primitive Layout =
29
32
  MapH = 120
30
33
  MapScale = 0.2
31
34
  Planets = 4
35
+ Walkers = 60
36
+ WalkMin = 500.0
37
+ WalkMax = 1300.0
32
38
 
33
39
  # Everything `update` and `draw` need. `bodies` and `names` are parallel (every
34
40
  # planet and moon, each planet followed by its moons), as are `halos` (the glow shown when selected).
@@ -39,6 +45,8 @@ record Sky(
39
45
  names: List[Str],
40
46
  sun: Node,
41
47
  label: Node,
48
+ crowd: Node,
49
+ front: Node?,
42
50
  selected: Int,
43
51
  clock: Float,
44
52
  paused: Bool,
@@ -50,6 +58,17 @@ fun spun(n: Node, speed: Float) -> Node =
50
58
  m.rotation = m.rotation + speed * dt
51
59
  n.setAct(turn)
52
60
 
61
+ # A walker's behaviour: it moves along x at `speed` and bobs on a wave of its x.
62
+ fun walking(n: Node, pace: Float, phase: Float) -> Node =
63
+ step: fn(Node, Float) := |m, dt|
64
+ nx := m.x + dt * pace
65
+ if nx > Layout.WalkMax
66
+ nx = 0.0 - Layout.WalkMin
67
+ if nx < 0.0 - Layout.WalkMin
68
+ nx = Layout.WalkMax
69
+ _ := m.at(nx, 510.0 + 50.0 * Math.sin(nx / 90.0 + phase))
70
+ n.setAct(step)
71
+
53
72
  # A body sprite and its (hidden) halo in a group `slot` centred on the group's own
54
73
  # origin. The caller places the slot; the sprite spins on its own axis.
55
74
  fun addBody(sky: Sky, sheet: Int, name: Str, row: Int, frame: Int, size: Float, spin: Float) -> Node =
@@ -76,7 +95,7 @@ fun newSky(sheet: Int) -> Sky =
76
95
  label := Node.text("Click a planet", 24.0, Color.White).at(16.0, 12.0)
77
96
  sky := Sky(
78
97
  stage: stage, bodies: List[Node](), halos: List[Node](), names: List[Str](),
79
- sun: sun, label: label, selected: -1, clock: 0.0, paused: false, map: -1)
98
+ sun: sun, label: label, crowd: Node.group(), front: null, selected: -1, clock: 0.0, paused: false, map: -1)
80
99
  # faint orbit rings, added first so every body draws (and is hit) above them
81
100
  for i := range Layout.Planets
82
101
  r := 90.0 + Float(i) * 60.0
@@ -94,6 +113,13 @@ fun newSky(sheet: Int) -> Sky =
94
113
  planet.add(moonOrbit)
95
114
  stage.root.add(orbit)
96
115
  stage.root.add(sun)
116
+ for i := range Layout.Walkers
117
+ speed := 20.0 + Float(i % 7) * 9.0
118
+ if i % 2 == 0
119
+ speed = 0.0 - speed
120
+ walker := Node.sprite(sheet, Float(i % 3) * Layout.Cell, Layout.Cell, Layout.Cell, Layout.Cell, 24.0, 24.0).at(Layout.WalkMax - 1800.0 + Float(i * 61 % 1800), 510.0)
121
+ sky.crowd.add(walking(walker, speed, Float(i)))
122
+ stage.root.add(sky.crowd)
97
123
  stage.root.add(label)
98
124
  sky
99
125
 
@@ -103,6 +129,7 @@ fun fit(sky: Sky, sw: Float, sh: Float) =
103
129
  sky.stage.camScale = zoom
104
130
  sky.stage.camX = 0.0 - {sw / zoom - Layout.Width} / 2.0
105
131
  sky.stage.camY = 0.0 - {sh / zoom - Layout.Height} / 2.0
132
+ sky.crowd.setCullingArea(sky.stage.camX, sky.stage.camY, sw / zoom, sh / zoom)
106
133
 
107
134
  # The index of a body (or its halo) in `bodies`, or -1.
108
135
  fun indexOf(sky: Sky, n: Node) -> Int =
@@ -127,6 +154,10 @@ fun clickAt(sky: Sky, x: Float, y: Float) =
127
154
  i := -1
128
155
  if n := sky.stage.hit(x, y)
129
156
  i = indexOf(sky, n)
157
+ if p := n.parent
158
+ if p == sky.crowd
159
+ n.toFront()
160
+ sky.front = n
130
161
  if i == sky.selected
131
162
  i = -1
132
163
  select(sky, i)
@@ -142,6 +173,9 @@ fun tick(sky: Sky, dt: Float) =
142
173
  return
143
174
  sky.clock = sky.clock + dt
144
175
  sky.stage.act(dt)
176
+ sky.crowd.sortChildren(|a, b| a.y < b.y)
177
+ if f := sky.front
178
+ f.toFront()
145
179
  sky.sun.srcX = Float(Int(sky.clock * 6.0) % 4) * Layout.Cell
146
180
 
147
181
  # --- host glue ---
@@ -264,3 +298,28 @@ test "fit centres the scene and screen points map back"
264
298
  p := sky.stage.screenToStage(800.0, 300.0)
265
299
  assert near(p.x, 400.0)
266
300
  assert near(p.y, 300.0)
301
+
302
+ test "the crowd is y-sorted every tick and culled against the view"
303
+ sky := newSky(-1)
304
+ tick(sky, 0.5)
305
+ tick(sky, 0.5)
306
+ kids := sky.crowd.children
307
+ assert kids.length() == 60
308
+ for i := range 59
309
+ assert kids.at(i).y <= kids.at(i + 1).y
310
+ fit(sky, 800.0, 600.0)
311
+ shown := flatten(sky.stage.root).length()
312
+ fit(sky, 100000.0, 600.0)
313
+ assert flatten(sky.stage.root).length() > shown
314
+ for i := range 60
315
+ assert kids.at(i).x >= 0.0 - 500.0 && kids.at(i).x <= 1300.0
316
+
317
+ test "clicking a walker brings it to the front, even after the next sort"
318
+ sky := newSky(-1)
319
+ fit(sky, 1800.0, 600.0)
320
+ w := sky.crowd.children.at(0)
321
+ p := w.localToStage(Vec2(x: 14.0, y: 14.0))
322
+ clickAt(sky, p.x, p.y)
323
+ assert w.zIndex() == 59
324
+ tick(sky, 0.1)
325
+ assert w.zIndex() == 59