From 3eda72e8c23372d9ef09c4cc79bd3040768214df Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Fri, 25 Sep 2026 15:02:45 +0300 Subject: [PATCH] wip(0.S3): packages and examples migrated in one run - ludic migrate state packages packages/ludic.lab/example/plate.ludic Co-Authored-By: Claude Opus 5.5 --- examples/events/answer.ludic | 22 +-- examples/events/layer_events.ludic | 14 +- examples/events/mod_events.ludic | 10 +- examples/events/recurse.ludic | 10 +- examples/events/scoped.ludic | 10 +- examples/functions/values.ludic | 12 +- examples/games/chronorift/combat.ludic | 88 +++++------ examples/games/chronorift/draw.ludic | 30 ++-- examples/games/chronorift/overworld.ludic | 72 ++++----- examples/games/chronorift/world.ludic | 26 ++-- examples/games/menu.ludic | 20 +-- examples/games/npcai_demo.ludic | 10 +- examples/games/platformer_demo.ludic | 26 ++-- examples/games/platformer_scaffolding.ludic | 22 +-- examples/games/rpg_demo.ludic | 30 ++-- examples/games/shooter_demo.ludic | 44 +++--- examples/games/snake.ludic | 118 +++++++-------- examples/lang/floats.ludic | 8 +- examples/lang/long_literals.ludic | 8 +- examples/lang/scenes.ludic | 24 +-- examples/library/combat_kit.ludic | 28 ++-- examples/library/component_access.ludic | 46 +++--- examples/library/gameplay_foundation.ludic | 20 +-- examples/library/input_auto.ludic | 8 +- examples/library/managers.ludic | 14 +- examples/library/prefabs.ludic | 10 +- examples/library/scene_menus.ludic | 10 +- examples/library/sprite_atlas.ludic | 18 ++- examples/library/sprite_render.ludic | 12 +- examples/library/threads.ludic | 28 ++-- examples/library/ui_anim.ludic | 30 ++-- examples/library/ui_box.ludic | 14 +- examples/library/ui_boxes.ludic | 10 +- examples/library/ui_calc.ludic | 12 +- examples/library/ui_component.ludic | 22 +-- examples/library/ui_controls.ludic | 54 +++---- examples/library/ui_css.ludic | 12 +- examples/library/ui_ease.ludic | 38 ++--- examples/library/ui_fill.ludic | 32 ++-- examples/library/ui_flex.ludic | 20 +-- examples/library/ui_focus_tip.ludic | 46 +++--- examples/library/ui_gamepad.ludic | 58 ++++---- examples/library/ui_hold.ludic | 70 ++++----- examples/library/ui_html.ludic | 28 ++-- examples/library/ui_inputs.ludic | 54 +++---- examples/library/ui_keycap.ludic | 66 ++++---- examples/library/ui_look.ludic | 54 +++---- examples/library/ui_pointer.ludic | 68 +++++---- examples/library/ui_popover.ludic | 50 +++---- examples/library/ui_popover_align.ludic | 10 +- examples/library/ui_popover_mouse.ludic | 38 ++--- examples/library/ui_react.ludic | 64 ++++---- examples/library/ui_reload.ludic | 20 +-- examples/library/ui_scroll_hold.ludic | 34 ++--- examples/library/ui_scrollbar.ludic | 68 +++++---- examples/library/ui_submit.ludic | 60 ++++---- examples/library/ui_templates.ludic | 50 ++++--- examples/library/ui_text.ludic | 18 ++- examples/library/ui_theme.ludic | 8 +- examples/library/ui_translate.ludic | 42 +++--- examples/library/ui_zoom.ludic | 12 +- examples/modules/bank/ledger.ludic | 18 ++- examples/modules/friend_of.ludic | 6 +- examples/modules/kinds/fishing/index.ludic | 16 +- examples/modules/kinds/hunting/index.ludic | 10 +- examples/modules/port_vars.ludic | 16 +- examples/modules/ports.ludic | 4 +- examples/modules/private_kinds.ludic | 4 +- examples/modules/private_twins.ludic | 4 +- examples/modules/station/app/index.ludic | 12 +- examples/modules/tangle.ludic | 6 +- examples/modules/tangle/a/index.ludic | 8 +- examples/modules/tangle/b/index.ludic | 6 +- examples/modules/twins/shop/index.ludic | 12 +- examples/modules/twins/weather/index.ludic | 12 +- examples/modules/visible.ludic | 14 +- examples/rendering/gl_triangle.ludic | 24 +-- examples/rendering/reload.ludic | 68 ++++----- examples/rendering/smooth.ludic | 126 ++++++++-------- examples/rendering/vk_compute.ludic | 74 ++++----- examples/rendering/vk_resources.ludic | 58 ++++---- examples/rendering/vk_triangle.ludic | 20 +-- packages/ludic.dungeon/dungeon.ludic | 58 ++++---- packages/ludic.gameplay/combat.ludic | 27 ++-- packages/ludic.gameplay/faction.ludic | 31 ++-- packages/ludic.lab/example/plate.ludic | 18 +-- packages/ludic.net/peers.ludic | 10 +- packages/ludic.net/receive.ludic | 2 +- packages/ludic.prefs/prefs.ludic | 80 +++++----- packages/ludic.rpg/crafting.ludic | 72 ++++----- packages/ludic.rpg/dialog.ludic | 98 ++++++------ packages/ludic.rpg/inventory.ludic | 110 +++++++++----- packages/ludic.rpg/quests.ludic | 106 ++++++------- packages/ludic.session/roster.ludic | 2 +- packages/ludic.shooter/shooter.ludic | 148 +++++++++--------- packages/ludic.ui/act.ludic | 12 +- packages/ludic.ui/anim.ludic | 50 +++---- packages/ludic.ui/attr.ludic | 48 +++--- packages/ludic.ui/attr_border.ludic | 4 +- packages/ludic.ui/attr_box.ludic | 50 +++---- packages/ludic.ui/attr_edge.ludic | 38 ++--- packages/ludic.ui/attr_fit.ludic | 4 +- packages/ludic.ui/attr_inset.ludic | 10 +- packages/ludic.ui/attr_more.ludic | 40 ++--- packages/ludic.ui/attr_place.ludic | 14 +- packages/ludic.ui/backend.ludic | 157 ++++++++++++++++++-- packages/ludic.ui/build.ludic | 52 +++---- packages/ludic.ui/build_class.ludic | 20 +-- packages/ludic.ui/build_more.ludic | 16 +- packages/ludic.ui/calc.ludic | 65 ++++---- packages/ludic.ui/calc_fns.ludic | 32 ++-- packages/ludic.ui/calc_terms.ludic | 70 ++++----- packages/ludic.ui/cascade.ludic | 60 ++++---- packages/ludic.ui/cascade_merge.ludic | 12 +- packages/ludic.ui/class.ludic | 45 +++--- packages/ludic.ui/color.ludic | 23 ++- packages/ludic.ui/controls.ludic | 48 +++--- packages/ludic.ui/controls_build.ludic | 20 +-- packages/ludic.ui/controls_key.ludic | 53 ++++--- packages/ludic.ui/controls_key_mouse.ludic | 24 +-- packages/ludic.ui/controls_number.ludic | 62 ++++---- packages/ludic.ui/controls_parts.ludic | 14 +- packages/ludic.ui/dev.ludic | 31 ++-- packages/ludic.ui/draw.ludic | 95 ++++++------ packages/ludic.ui/draw_content.ludic | 44 +++--- packages/ludic.ui/draw_gradient.ludic | 19 ++- packages/ludic.ui/draw_order.ludic | 39 +++-- packages/ludic.ui/elem.ludic | 30 ++-- packages/ludic.ui/env.ludic | 23 ++- packages/ludic.ui/eval.ludic | 46 +++--- packages/ludic.ui/focus.ludic | 97 ++++++------ packages/ludic.ui/frame.ludic | 94 ++++++------ packages/ludic.ui/frame_box.ludic | 19 ++- packages/ludic.ui/hold.ludic | 29 ++-- packages/ludic.ui/input.ludic | 32 ++-- packages/ludic.ui/input_pad.ludic | 46 +++--- packages/ludic.ui/instance.ludic | 24 ++- packages/ludic.ui/layout.ludic | 11 +- packages/ludic.ui/layout_box.ludic | 20 +-- packages/ludic.ui/layout_run.ludic | 24 ++- packages/ludic.ui/layout_text.ludic | 22 +-- packages/ludic.ui/layout_wrap.ludic | 36 ++--- packages/ludic.ui/lib.ludic | 51 +++---- packages/ludic.ui/lib_util.ludic | 8 +- packages/ludic.ui/life.ludic | 65 ++++---- packages/ludic.ui/link.ludic | 18 +-- packages/ludic.ui/media.ludic | 10 +- packages/ludic.ui/native.ludic | 60 ++++---- packages/ludic.ui/pointer.ludic | 56 ++++--- packages/ludic.ui/pointer_events.ludic | 48 +++--- packages/ludic.ui/popover.ludic | 19 ++- packages/ludic.ui/popover_anchor.ludic | 26 ++-- packages/ludic.ui/popover_within.ludic | 24 ++- packages/ludic.ui/reload.ludic | 32 ++-- packages/ludic.ui/render3d.ludic | 28 ++-- packages/ludic.ui/render3d_image.ludic | 84 +++++------ packages/ludic.ui/render3d_nine.ludic | 4 +- packages/ludic.ui/screen_class.ludic | 16 +- packages/ludic.ui/scroll.ludic | 66 ++++---- packages/ludic.ui/scroll_bar.ludic | 64 ++++---- packages/ludic.ui/scroll_held.ludic | 24 ++- packages/ludic.ui/select_match.ludic | 32 ++-- packages/ludic.ui/state.ludic | 26 ++-- packages/ludic.ui/style.ludic | 30 ++-- packages/ludic.ui/text_fit.ludic | 21 ++- packages/ludic.ui/tooltip.ludic | 43 +++--- packages/ludic.ui/tooltip_text.ludic | 22 ++- packages/ludic.ui/tpl.ludic | 10 +- packages/ludic.ui/tpl_lines.ludic | 17 +-- packages/ludic.ui/transition.ludic | 38 +++-- packages/ludic.ui/units.ludic | 36 ++--- packages/ludic.ui/zoom.ludic | 18 +-- 172 files changed, 3094 insertions(+), 2966 deletions(-) diff --git a/examples/events/answer.ludic b/examples/events/answer.ludic index 255ccb3b..ef5597e3 100644 --- a/examples/events/answer.ludic +++ b/examples/events/answer.ludic @@ -5,20 +5,22 @@ # Running it prints: 10 20 0 program Answer { event cancellable Ask { kind: int } - var answer: int = 0 + state AnswerState { + answer: int = 0 + } - @On(Ask) handler One { if kind != 1 { return }; answer = 10; cancel } - @On(Ask) handler Two { if kind != 2 { return }; answer = 20; cancel } + @On(Ask) handler One(answer_st: mut AnswerState) { if kind != 1 { return }; answer_st.answer = 10; cancel } + @On(Ask) handler Two(answer_st: mut AnswerState) { if kind != 2 { return }; answer_st.answer = 20; cancel } - function ask(k: int) -> int { - answer = 0 - if emit Ask(kind: k) != 0 { return answer } + function ask(answer_st: mut AnswerState, k: int) -> int { + answer_st.answer = 0 + if emit Ask(kind: k) != 0 { return answer_st.answer } return 0 } - entry { - print(ask(1)) - print(ask(2)) - print(ask(3)) + entry (answer_st: mut AnswerState) { + print(ask(answer_st, 1)) + print(ask(answer_st, 2)) + print(ask(answer_st, 3)) } } diff --git a/examples/events/layer_events.ludic b/examples/events/layer_events.ludic index ab99630c..ac6c12d0 100644 --- a/examples/events/layer_events.ludic +++ b/examples/events/layer_events.ludic @@ -6,7 +6,9 @@ # # Running it prints: 50 2 1 50 program LayerEvents { - var step: int = 0 + state LayereventsState { + step: int = 0 + } @On(layer_Hud_show) handler Shown { print(1) } # a mod reacts when the HUD returns @On(layer_Hud_hide) handler Hidden { print(2) } # ...and when it's hidden @@ -16,11 +18,11 @@ program LayerEvents { handler Draw phase Update { print(50) } # only runs while Hud is enabled } layer Ctrl { - handler Drive phase LateUpdate { - step += 1 - if step == 1 { disable layer Hud } # frame 1: hide -> Hidden 2, Draw stops - if step == 2 { enable layer Hud } # frame 2: show -> Shown 1, Draw resumes - if step == 3 { quit() } # frame 3: after Draw ran again + handler Drive(layerevents_st: mut LayereventsState) phase LateUpdate { + layerevents_st.step += 1 + if layerevents_st.step == 1 { disable layer Hud } # frame 1: hide -> Hidden 2, Draw stops + if layerevents_st.step == 2 { enable layer Hud } # frame 2: show -> Shown 1, Draw resumes + if layerevents_st.step == 3 { quit() } # frame 3: after Draw ran again } } } diff --git a/examples/events/mod_events.ludic b/examples/events/mod_events.ludic index f1b3dc23..1cf5d3da 100644 --- a/examples/events/mod_events.ludic +++ b/examples/events/mod_events.ludic @@ -3,14 +3,16 @@ # `Damage` has two @On listeners: one prints the amount, one accumulates a total. # Emitting it twice runs both listeners each time. Prints 10 / 32 / 42. program ModEvents { - var total: int = 0 + state ModeventsState { + total: int = 0 + } event Damage { amount: int = 0 } @On(Damage) handler Native { print(amount) } # prints each hit - @On(Damage) handler Accum { total += amount } # sums them + @On(Damage) handler Accum(modevents_st: mut ModeventsState) { modevents_st.total += amount } # sums them - entry { + entry (modevents_st: ModeventsState) { emit Damage(amount: 10) # Native prints 10 emit Damage(amount: 32) # Native prints 32 - print(total) # 42 + print(modevents_st.total) # 42 } } diff --git a/examples/events/recurse.ludic b/examples/events/recurse.ludic index e1f8be69..fdb4288a 100644 --- a/examples/events/recurse.ludic +++ b/examples/events/recurse.ludic @@ -6,16 +6,18 @@ # # Running it prints: 16 (the cycle bottoms out at the depth cap, deterministically) program Recurse { - var n: int = 0 + state RecurseState { + n: int = 0 + } event Ping { } event Pong { } - @On(Ping) handler A { n += 1; emit Pong() } # each Ping deepens by one Pong + @On(Ping) handler A(recurse_st: mut RecurseState) { recurse_st.n += 1; emit Pong() } # each Ping deepens by one Pong @On(Pong) handler B { emit Ping() } # ...and each Pong by one Ping - entry { + entry (recurse_st: RecurseState) { emit Ping() - print(n) + print(recurse_st.n) } } diff --git a/examples/events/scoped.ludic b/examples/events/scoped.ludic index 6d54bfe5..559a9a3a 100644 --- a/examples/events/scoped.ludic +++ b/examples/events/scoped.ludic @@ -6,16 +6,18 @@ program Scoped { property Health { hp: int = 0 } model Unit { Health } - var fires: int = 0 + state ScopedState { + fires: int = 0 + } event Tick { } - @On(Tick) handler OnTick { for (Health) in query [Health, {Unit}] { fires += 1 } } + @On(Tick) handler OnTick(scoped_st: mut ScopedState) { for (Health) in query [Health, {Unit}] { scoped_st.fires += 1 } } - entry { + entry (scoped_st: ScopedState) { spawn Unit { Health { hp: 1 } } emit Tick() # 1 live Unit -> fires = 1 emit Tick() # fires = 2 for (Health) in query [Health, {Unit}] { despawn self() } # kill the Unit emit Tick() # no live Units -> fires stays 2 - print(fires) # 2 + print(scoped_st.fires) # 2 } } diff --git a/examples/functions/values.ludic b/examples/functions/values.ludic index 658bbd16..f2e87943 100644 --- a/examples/functions/values.ludic +++ b/examples/functions/values.ludic @@ -9,12 +9,14 @@ program FunctionValues { function double(x: int) -> int { return x * 2 } function square(x: int) -> int { return x * x } function half(x: float) -> float { return x / 2.0 } - var op: fn(int) -> int = null + state FunctionvaluesState { + op: fn(int) -> int = null + } function run_with(f: fn(int) -> int, v: int) -> int { return f(v) } function pass_on(f: fn(float) -> float) -> fn(float) -> float { return f } - entry { - op = fn double - print(op(21)) + entry (functionvalues_st: mut FunctionvaluesState) { + functionvalues_st.op = fn double + print(functionvalues_st.op(21)) let ops = new []fn(int) -> int push(ops, fn double) push(ops, fn square) @@ -26,6 +28,6 @@ program FunctionValues { print(run_with(fn square, 9)) let h = pass_on(fn half) print(h(3.0)) - if op != null { print(1) } + if functionvalues_st.op != null { print(1) } } } diff --git a/examples/games/chronorift/combat.ludic b/examples/games/chronorift/combat.ludic index b5bb0e4e..381c24a5 100644 --- a/examples/games/chronorift/combat.ludic +++ b/examples/games/chronorift/combat.ludic @@ -8,43 +8,43 @@ enum KnightAct { Attack, Guard, Item, Flee } enum MageAct { Attack, Heal, Guard } -handler Battle phase Update { - if game_mode == 1 { +handler Battle(games_chronorift_st: mut GamesChronoriftState) phase Update { + if games_chronorift_st.game_mode == 1 { let k = Input.key() for (e) in query [Stats, {Enemy}] { - machine turn_phase { + machine games_chronorift_st.turn_phase { state KnightMenu { for (st, pt) in query [Stats, Party] { if pt.slot == 0 { if st.hp <= 0 { become MageMenu - menu_cursor = 0 + games_chronorift_st.menu_cursor = 0 } else { - if k == 'w' { menu_cursor = max(0, menu_cursor - 1) } - if k == 's' { menu_cursor = min(3, menu_cursor + 1) } + if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) } + if k == 's' { games_chronorift_st.menu_cursor = min(3, games_chronorift_st.menu_cursor + 1) } if is_confirm(k) { - match menu_cursor { + match games_chronorift_st.menu_cursor { KnightAct.Attack => { let d = max(1, st.atk - e.def + Random.range(0, 4)) e.hp -= d - player_hit = d + games_chronorift_st.player_hit = d } - KnightAct.Guard => { st.guard = 1; player_hit = 0 } + KnightAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 } KnightAct.Item => { - if potions > 0 { - potions -= 1 + if games_chronorift_st.potions > 0 { + games_chronorift_st.potions -= 1 st.hp = min(st.maxhp, st.hp + 24) - player_hit = 24 - } else { player_hit = 0 } + games_chronorift_st.player_hit = 24 + } else { games_chronorift_st.player_hit = 0 } } KnightAct.Flee => { - if Random.chance(50) { e.hp = 0; game_mode = 0 } - else { player_hit = 0 } + if Random.chance(50) { e.hp = 0; games_chronorift_st.game_mode = 0 } + else { games_chronorift_st.player_hit = 0 } } } become KnightResolve - menu_cursor = 0 + games_chronorift_st.menu_cursor = 0 } } } @@ -55,13 +55,13 @@ handler Battle phase Update { if k != 0 { if e.hp <= 0 { grant_xp(e.xp) - gold += e.xp - if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 } - else { game_mode = 0 } + games_chronorift_st.gold += e.xp + if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 } + else { games_chronorift_st.game_mode = 0 } become KnightMenu } else { become MageMenu - menu_cursor = 0 + games_chronorift_st.menu_cursor = 0 } } } @@ -72,14 +72,14 @@ handler Battle phase Update { if st.hp <= 0 { become EnemyTurn } else { - if k == 'i' { menu_cursor = max(0, menu_cursor - 1) } - if k == 'k' { menu_cursor = min(2, menu_cursor + 1) } + if k == 'i' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) } + if k == 'k' { games_chronorift_st.menu_cursor = min(2, games_chronorift_st.menu_cursor + 1) } if k == 'j' { - match menu_cursor { + match games_chronorift_st.menu_cursor { MageAct.Attack => { let d = max(1, st.atk + 5 - e.def + Random.range(0, 5)) e.hp -= d - player_hit = d + games_chronorift_st.player_hit = d } MageAct.Heal => { if st.mp >= 4 { @@ -87,13 +87,13 @@ handler Battle phase Update { for (t2, p2) in query [Stats, Party] { if p2.slot == 0 { t2.hp = min(t2.maxhp, t2.hp + 16) } } - player_hit = 16 - } else { player_hit = 0 } + games_chronorift_st.player_hit = 16 + } else { games_chronorift_st.player_hit = 0 } } - MageAct.Guard => { st.guard = 1; player_hit = 0 } + MageAct.Guard => { st.guard = 1; games_chronorift_st.player_hit = 0 } } become MageResolve - menu_cursor = 0 + games_chronorift_st.menu_cursor = 0 } } } @@ -104,9 +104,9 @@ handler Battle phase Update { if k != 0 { if e.hp <= 0 { grant_xp(e.xp) - gold += e.xp - if enemy_type == 2 { boss_down = 1; game_mode = 3; ack = 0 } - else { game_mode = 0 } + games_chronorift_st.gold += e.xp + if games_chronorift_st.enemy_type == 2 { games_chronorift_st.boss_down = 1; games_chronorift_st.game_mode = 3; games_chronorift_st.ack = 0 } + else { games_chronorift_st.game_mode = 0 } become KnightMenu } else { become EnemyTurn @@ -123,7 +123,7 @@ handler Battle phase Update { var ed = max(1, e.atk - t3.def + Random.range(0, 4)) if t3.guard == 1 { ed /= 2; t3.guard = 0 } t3.hp -= ed - enemy_hit = ed + games_chronorift_st.enemy_hit = ed done = 1 } } @@ -132,10 +132,10 @@ handler Battle phase Update { for (t4) in query [Stats, {Party}] { if t4.hp > 0 { alive += 1 } } - if alive == 0 { game_mode = 2; ack = 0 } + if alive == 0 { games_chronorift_st.game_mode = 2; games_chronorift_st.ack = 0 } else { become KnightMenu - menu_cursor = 0 + games_chronorift_st.menu_cursor = 0 } } } @@ -144,29 +144,29 @@ handler Battle phase Update { } } -handler CleanBattle phase LateUpdate { - if game_mode != 1 { +handler CleanBattle(games_chronorift_st: GamesChronoriftState) phase LateUpdate { + if games_chronorift_st.game_mode != 1 { for (e) in query [Stats, {Enemy}] { despawn self() } } } -handler Meta phase Update { +handler Meta(games_chronorift_st: mut GamesChronoriftState) phase Update { let k = Input.key() - if game_mode == 2 { - if k == 0 { ack = 1 } + if games_chronorift_st.game_mode == 2 { + if k == 0 { games_chronorift_st.ack = 1 } if k != 0 { - if ack == 1 { + if games_chronorift_st.ack == 1 { for (h) in query [Stats, {Party}] { h.hp = h.maxhp } - game_mode = 0 + games_chronorift_st.game_mode = 0 } } } - if game_mode == 3 { - if k == 0 { ack = 1 } + if games_chronorift_st.game_mode == 3 { + if k == 0 { games_chronorift_st.ack = 1 } if k != 0 { - if ack == 1 { game_mode = 0 } + if games_chronorift_st.ack == 1 { games_chronorift_st.game_mode = 0 } } } } diff --git a/examples/games/chronorift/draw.ludic b/examples/games/chronorift/draw.ludic index c10eee0b..c65ea164 100644 --- a/examples/games/chronorift/draw.ludic +++ b/examples/games/chronorift/draw.ludic @@ -1,7 +1,7 @@ # draw.ludic — the Render system (overworld, battle, and overlay screens). -handler Draw phase Render { - let m = game_mode +handler Draw(games_chronorift_st: GamesChronoriftState) phase Render { + let m = games_chronorift_st.game_mode Screen.clear(0x101018) if m == 0 { @@ -31,7 +31,7 @@ handler Draw phase Render { if c == 'D' { Screen.sprite(SPR_DOOR, sx, sy) } if c == 'S' { Screen.sprite(SPR_DOOR, sx, sy) } if c == 'B' { - if boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) } + if games_chronorift_st.boss_down == 0 { Screen.sprite(SPR_BOSS, sx, sy) } } } } @@ -47,9 +47,9 @@ handler Draw phase Render { Screen.draw_text(8, 18, "HP", 0x88ff88, 1) Screen.draw_number(24, 18, h.hp, 0xffffff, 1) Screen.draw_text(8, 28, "G", 0xffd24a, 1) - Screen.draw_number(18, 28, gold, 0xffffff, 1) + Screen.draw_number(18, 28, games_chronorift_st.gold, 0xffffff, 1) Screen.draw_text(54, 28, "PO", 0x88ffcc, 1) - Screen.draw_number(72, 28, potions, 0xffffff, 1) + Screen.draw_number(72, 28, games_chronorift_st.potions, 0xffffff, 1) } } } @@ -58,13 +58,13 @@ handler Draw phase Render { Screen.fill_rectangle(0, 0, 320, 176, 0x241a2e) for (e, a) in query [Stats, Actor, {Enemy}] { var bs = 5 - if enemy_type == 2 { bs = 7 } + if games_chronorift_st.enemy_type == 2 { bs = 7 } Screen.sprite_scaled(a.kind, 160 - bs * 8, 44, bs) - Screen.draw_text(100, 20, enemy_name(enemy_type), 0xffffff, 1) + Screen.draw_text(100, 20, enemy_name(games_chronorift_st.enemy_type), 0xffffff, 1) Screen.fill_rectangle(100, 30, 120, 8, 0x401018) Screen.fill_rectangle(100, 30, e.hp * 120 / e.maxhp, 8, 0xe04040) } - let ph = turn_phase + let ph = games_chronorift_st.turn_phase var act = 0 if ph >= 2 { act = 1 } if ph >= 4 { act = 2 } @@ -83,14 +83,14 @@ handler Draw phase Render { Screen.draw_text(hx + 36, 207, "MP", 0x88aaff, 1) Screen.draw_number(hx + 56, 207, st.mp, 0xffffff, 1) } - let cur = menu_cursor + let cur = games_chronorift_st.menu_cursor if ph == 0 or ph == 1 { Screen.draw_text(206, 181, "P1 KNIGHT", 0xffe08a, 1) Screen.draw_text(214, 192, "ATTACK", 0xffffff, 1) Screen.draw_text(214, 203, "DEFEND", 0xffffff, 1) Screen.draw_text(214, 214, "ITEM", 0xffffff, 1) Screen.draw_text(256, 214, "x", 0x777777, 1) - Screen.draw_number(264, 214, potions, 0xffd24a, 1) + Screen.draw_number(264, 214, games_chronorift_st.potions, 0xffd24a, 1) Screen.draw_text(214, 225, "RUN", 0xffffff, 1) if ph == 0 { Screen.draw_text(204, 192 + cur * 11, ">", 0xffff00, 1) } } @@ -104,11 +104,11 @@ handler Draw phase Render { if ph == 4 { Screen.draw_text(206, 200, "ENEMY TURN", 0xff8888, 1) } if ph == 1 or ph == 3 { Screen.draw_text(6, 232, "DMG", 0xffff88, 1) - Screen.draw_number(34, 232, player_hit, 0xffffff, 1) + Screen.draw_number(34, 232, games_chronorift_st.player_hit, 0xffffff, 1) } if ph == 4 { Screen.draw_text(6, 232, "FOE HITS", 0xff8888, 1) - Screen.draw_number(66, 232, enemy_hit, 0xffffff, 1) + Screen.draw_number(66, 232, games_chronorift_st.enemy_hit, 0xffffff, 1) } } @@ -128,11 +128,11 @@ handler Draw phase Render { Screen.draw_text(80, 104, "BUY POTION", 0xffffff, 1) Screen.draw_text(180, 104, "10 G", 0xffd24a, 1) Screen.draw_text(80, 122, "LEAVE", 0xffffff, 1) - Screen.draw_text(70, 104 + menu_cursor * 18, ">", 0xffff00, 1) + Screen.draw_text(70, 104 + games_chronorift_st.menu_cursor * 18, ">", 0xffff00, 1) Screen.draw_text(80, 150, "GOLD", 0xffd24a, 1) - Screen.draw_number(120, 150, gold, 0xffffff, 1) + Screen.draw_number(120, 150, games_chronorift_st.gold, 0xffffff, 1) Screen.draw_text(160, 150, "POTIONS", 0x88ffcc, 1) - Screen.draw_number(226, 150, potions, 0xffffff, 1) + Screen.draw_number(226, 150, games_chronorift_st.potions, 0xffffff, 1) } Screen.show() } diff --git a/examples/games/chronorift/overworld.ludic b/examples/games/chronorift/overworld.ludic index 87cb851b..9c37e5a8 100644 --- a/examples/games/chronorift/overworld.ludic +++ b/examples/games/chronorift/overworld.ludic @@ -1,6 +1,6 @@ # overworld.ludic — boot, movement/encounter input, and the shop system. -handler Boot phase Start { +handler Boot(games_chronorift_st: mut GamesChronoriftState) phase Start { png_load("assets/kenney/tiny-town/Tiles/tile_0000.png") # 0 grass png_load("assets/kenney/tiny-town/Tiles/tile_0004.png") # 1 tree png_load("assets/kenney/tiny-town/Tiles/tile_0025.png") # 2 path @@ -25,16 +25,16 @@ handler Boot phase Start { Party { slot: 1 } Stats { hp: 26, maxhp: 26, mp: 16, maxmp: 16, atk: 8, def: 4, lvl: 1, xp: 0, guard: 0 } } - game_mode = 0 - encounter = 4 - world_map = 0 - boss_down = 0 - gold = 20 - potions = 2 + games_chronorift_st.game_mode = 0 + games_chronorift_st.encounter = 4 + games_chronorift_st.world_map = 0 + games_chronorift_st.boss_down = 0 + games_chronorift_st.gold = 20 + games_chronorift_st.potions = 2 } -handler Control phase Input { - if game_mode == 0 { +handler Control(games_chronorift_st: mut GamesChronoriftState) phase Input { + if games_chronorift_st.game_mode == 0 { for (p) in query [Pos, {Player}] { let k = Input.key() var nx = p.x @@ -57,43 +57,43 @@ handler Control phase Input { build_dungeon(0) p.x = 3 p.y = 2 - world_map = 1 + games_chronorift_st.world_map = 1 } if tc == 'S' { build_field(0) p.x = 36 p.y = 3 - world_map = 0 + games_chronorift_st.world_map = 0 } if tc == 'B' { - if boss_down == 0 { - enemy_type = 2 + if games_chronorift_st.boss_down == 0 { + games_chronorift_st.enemy_type = 2 spawn Enemy { Actor { kind: 8 } Stats { hp: 120, maxhp: 120, mp: 0, maxmp: 0, atk: 18, def: 10, lvl: 5, xp: 300, guard: 0 } } - game_mode = 1 - turn_phase = 0 - menu_cursor = 0 + games_chronorift_st.game_mode = 1 + games_chronorift_st.turn_phase = 0 + games_chronorift_st.menu_cursor = 0 } } if tc == 'h' { - game_mode = 4 - menu_cursor = 0 + games_chronorift_st.game_mode = 4 + games_chronorift_st.menu_cursor = 0 } if tc != 'D' { if tc != 'S' { if tc != 'B' { if tc != 'h' { - encounter -= 1 - if encounter <= 0 { - encounter = Random.range(4, 9) + games_chronorift_st.encounter -= 1 + if games_chronorift_st.encounter <= 0 { + games_chronorift_st.encounter = Random.range(4, 9) var et = 0 - if world_map == 1 { et = 1 } - if world_map == 0 { + if games_chronorift_st.world_map == 1 { et = 1 } + if games_chronorift_st.world_map == 0 { if Random.chance(40) { et = 1 } } - enemy_type = et + games_chronorift_st.enemy_type = et if et == 0 { spawn Enemy { Actor { kind: 6 } @@ -106,9 +106,9 @@ handler Control phase Input { Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 } } } - game_mode = 1 - turn_phase = 0 - menu_cursor = 0 + games_chronorift_st.game_mode = 1 + games_chronorift_st.turn_phase = 0 + games_chronorift_st.menu_cursor = 0 } } } @@ -122,19 +122,19 @@ handler Control phase Input { } # shop (at the house 'h' tile): buy potions with gold -handler Shop phase Update { - if game_mode == 4 { +handler Shop(games_chronorift_st: mut GamesChronoriftState) phase Update { + if games_chronorift_st.game_mode == 4 { let k = Input.key() - if k == 'w' { menu_cursor = max(0, menu_cursor - 1) } - if k == 's' { menu_cursor = min(1, menu_cursor + 1) } + if k == 'w' { games_chronorift_st.menu_cursor = max(0, games_chronorift_st.menu_cursor - 1) } + if k == 's' { games_chronorift_st.menu_cursor = min(1, games_chronorift_st.menu_cursor + 1) } if is_confirm(k) { - if menu_cursor == 0 { - if gold >= 10 { - gold -= 10 - potions += 1 + if games_chronorift_st.menu_cursor == 0 { + if games_chronorift_st.gold >= 10 { + games_chronorift_st.gold -= 10 + games_chronorift_st.potions += 1 } } - if menu_cursor == 1 { game_mode = 0 } + if games_chronorift_st.menu_cursor == 1 { games_chronorift_st.game_mode = 0 } } } } diff --git a/examples/games/chronorift/world.ludic b/examples/games/chronorift/world.ludic index 023c9a08..7eca4fb4 100644 --- a/examples/games/chronorift/world.ludic +++ b/examples/games/chronorift/world.ludic @@ -29,15 +29,17 @@ const SPR_MAGE: int = 9 const SPR_FLOOR: int = 10 const SPR_WALL: int = 11 -var game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory -var turn_phase: int = 0 -var menu_cursor: int = 0 -var encounter: int = 0 -var enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss -var player_hit: int = 0 -var enemy_hit: int = 0 -var gold: int = 0 -var world_map: int = 0 # 0 overworld, 1 dungeon -var boss_down: int = 0 # boss defeated flag -var ack: int = 0 # debounce for end screens -var potions: int = 0 # potions in inventory +export state GamesChronoriftState { + game_mode: int = 0 # 0 field, 1 battle, 2 gameover, 3 victory + turn_phase: int = 0 + menu_cursor: int = 0 + encounter: int = 0 + enemy_type: int = 0 # 0 slime, 1 skeleton, 2 boss + player_hit: int = 0 + enemy_hit: int = 0 + gold: int = 0 + world_map: int = 0 # 0 overworld, 1 dungeon + boss_down: int = 0 # boss defeated flag + ack: int = 0 # debounce for end screens + potions: int = 0 # potions in inventory +} diff --git a/examples/games/menu.ludic b/examples/games/menu.ludic index 8a841778..5e256c6e 100644 --- a/examples/games/menu.ludic +++ b/examples/games/menu.ludic @@ -9,22 +9,24 @@ program Menu { const FONT_PATH: string = "/System/Library/Fonts/Supplemental/Arial.ttf" const BACKDROP: int = 0x0e0e16 - var title_font: int = 0 + state MenuState { + title_font: int = 0 + } ui MainMenu { panel id: Root w: 288 pad: 16 gap: 6 bg: 0x1a1a2c border: 0x4a4a72 skin: "assets/ui/panel.png" inset: 10 align: center { - label text: "CHRONO RIFT" font: title_font size: 26 fg: 0xffe060 align: center - label text: "a Ludic retained-UI demo" font: title_font size: 14 fg: 0x9aa0c8 align: center + label text: "CHRONO RIFT" font: MenuState.title_font size: 26 fg: 0xffe060 align: center + label text: "a Ludic retained-UI demo" font: MenuState.title_font size: 14 fg: 0x9aa0c8 align: center spacer h: 4 - button id: NewGame text: "New Game" font: title_font size: 16 w: 236 - button id: Continue text: "Continue" font: title_font size: 16 w: 236 - button id: Options text: "Options" font: title_font size: 16 w: 236 - button id: Quit text: "Quit" font: title_font size: 16 w: 236 + button id: NewGame text: "New Game" font: MenuState.title_font size: 16 w: 236 + button id: Continue text: "Continue" font: MenuState.title_font size: 16 w: 236 + button id: Options text: "Options" font: MenuState.title_font size: 16 w: 236 + button id: Quit text: "Quit" font: MenuState.title_font size: 16 w: 236 } } - handler Boot phase Start { - title_font = Font.load(FONT_PATH) + handler Boot(menu_st: mut MenuState) phase Start { + menu_st.title_font = Font.load(FONT_PATH) Ui.build() Ui.open(UI_MainMenu) } diff --git a/examples/games/npcai_demo.ludic b/examples/games/npcai_demo.ludic index 5fc48687..481347b8 100644 --- a/examples/games/npcai_demo.ludic +++ b/examples/games/npcai_demo.ludic @@ -33,8 +33,10 @@ program NpcAiDemo { @OnSpawn(Ally) handler AInit { } @OnSpawn(Dummy) handler DInit { } - var spotted: int = 0 - @On(TargetSpotted) handler OnSpot { spotted += 1 } + state NpcaidemoState { + spotted: int = 0 + } + @On(TargetSpotted) handler OnSpot(npcaidemo_st: mut NpcaidemoState) { npcaidemo_st.spotted += 1 } function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } } function bi(b: bool) -> int { if b { return 1 }; return 0 } @@ -55,7 +57,7 @@ program NpcAiDemo { return -1 } - entry { + entry (npcaidemo_st: NpcaidemoState) { let PBr = World.prop_id("Brain") Weapon.def("blaster", 4, 10, 10, 0, 1, 0) @@ -72,7 +74,7 @@ program NpcAiDemo { # ---- A. perception: the enemy spots the hostile dummy ------------------- tick_n(4) - print(bi(spotted >= 1)) # 1 — TargetSpotted fired + print(bi(npcaidemo_st.spotted >= 1)) # 1 — TargetSpotted fired print(bi(Ai.target(enemy) == dummy)) # 1 — remembers the right target # ---- B. FSM attack: in range -> auto-aim + fire -> dummy takes damage --- diff --git a/examples/games/platformer_demo.ludic b/examples/games/platformer_demo.ludic index d6768a47..eb529b40 100644 --- a/examples/games/platformer_demo.ludic +++ b/examples/games/platformer_demo.ludic @@ -32,13 +32,15 @@ program PlatformerDemo { @OnSpawn(Floor) handler FloorInit { } @OnSpawn(Wall) handler WallInit { } - var jumps: int = 0 - @On(JumpPerformed) handler CountJumps { jumps += 1 } + state PlatformerdemoState { + jumps: int = 0 + have_power: int = 0 + vetoed_air: int = 0 + } + @On(JumpPerformed) handler CountJumps(platformerdemo_st: mut PlatformerdemoState) { platformerdemo_st.jumps += 1 } # a game rule: forbid the air (double) jump unless a "power" flag is set — pure # lever-4 veto, no controller edit. - var have_power: int = 0 - var vetoed_air: int = 0 - @On(JumpRequested) handler GateAir { if (kind == 2) and (have_power == 0) { vetoed_air = 1; cancel } } + @On(JumpRequested) handler GateAir(platformerdemo_st: mut PlatformerdemoState) { if (kind == 2) and (platformerdemo_st.have_power == 0) { platformerdemo_st.vetoed_air = 1; cancel } } function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } } function bi(b: bool) -> int { if b { return 1 }; return 0 } @@ -47,7 +49,7 @@ program PlatformerDemo { return World.get(e, P, World.field_id(P, "coyote_t")) } - entry { + entry (platformerdemo_st: mut PlatformerdemoState) { let PB = World.prop_id("Body") let PP = World.prop_id("Position") let PL = World.prop_id("Platformer") @@ -89,16 +91,16 @@ program PlatformerDemo { print(bi(pf_coyote(h) == 6)) # 1 # ---- D. double jump gated by the veto hook ----------------------------- - have_power = 0 + platformerdemo_st.have_power = 0 Platformer.jump(h); tick_n(3) # ground jump -> airborne - let after_ground = jumps + let after_ground = platformerdemo_st.jumps Platformer.jump(h); tick_n(2) # air jump attempt -> vetoed - print(bi((jumps == after_ground) and (vetoed_air == 1))) # 1 + print(bi((platformerdemo_st.jumps == after_ground) and (platformerdemo_st.vetoed_air == 1))) # 1 - have_power = 1 - let before_power = jumps + platformerdemo_st.have_power = 1 + let before_power = platformerdemo_st.jumps Platformer.jump(h); tick_n(2) # air jump now allowed - print(bi(jumps == before_power + 1)) # 1 + print(bi(platformerdemo_st.jumps == before_power + 1)) # 1 tick_n(60) # settle on the floor # ---- E. horizontal run into the wall (collision reuse) ----------------- diff --git a/examples/games/platformer_scaffolding.ludic b/examples/games/platformer_scaffolding.ludic index 5f1b9743..54be3989 100644 --- a/examples/games/platformer_scaffolding.ludic +++ b/examples/games/platformer_scaffolding.ludic @@ -33,17 +33,19 @@ program PlatformerScaffolding { @OnSpawn(Bouncer) handler BI { } @OnSpawn(Spike) handler SI { } - var picked: int = 0 - @On(ItemPickedUp) handler OnPick { picked += 1 } - var sprang: int = 0 - @On(Sprang) handler OnSpring { sprang += 1 } - var hurt: int = 0 - @On(Hurt) handler OnHurt { hurt += 1 } + state PlatformerscaffoldingState { + picked: int = 0 + sprang: int = 0 + hurt: int = 0 + } + @On(ItemPickedUp) handler OnPick(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.picked += 1 } + @On(Sprang) handler OnSpring(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.sprang += 1 } + @On(Hurt) handler OnHurt(platformerscaffolding_st: mut PlatformerscaffoldingState) { platformerscaffolding_st.hurt += 1 } function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } } function bi(b: bool) -> int { if b { return 1 }; return 0 } - entry { + entry (platformerscaffolding_st: PlatformerscaffoldingState) { let PP = World.prop_id("Position") let PB = World.prop_id("Body") let PL = World.prop_id("Platformer") @@ -69,7 +71,7 @@ program PlatformerScaffolding { spawn Coin { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) }, Collider { w: 10, h: 10, is_trigger: 1 }, Pickup { kind: 0, value: 5 } } tick_n(2) - print(bi(picked == 1)) # 1 — ItemPickedUp fired + print(bi(platformerscaffolding_st.picked == 1)) # 1 — ItemPickedUp fired print(bi(Score.get(h) == 5)) # 1 — value scored # ---- spring launches the hero upward ----------------------------------- @@ -79,13 +81,13 @@ program PlatformerScaffolding { let vy_before = World.get(h, PB, World.field_id(PB, "vy")) tick_n(2) let vy_after = World.get(h, PB, World.field_id(PB, "vy")) - print(bi((sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up) + print(bi((platformerscaffolding_st.sprang >= 1) and (vy_after < 0))) # 1 — launched (vy negative = up) # ---- hazard damages a Life -------------------------------------------- spawn Spike { Position { x: World.get(h, PP, fx), y: World.get(h, PP, fy) }, Collider { w: 12, h: 12, is_trigger: 1 }, Hazard { dmg: 1, knockback: 6 } } tick_n(2) - print(bi(hurt >= 1)) # 1 — Hurt fired + print(bi(platformerscaffolding_st.hurt >= 1)) # 1 — Hurt fired let PLife = World.prop_id("Life") print(bi(World.get(h, PLife, World.field_id(PLife, "hp")) < 3)) # 1 — hp dropped diff --git a/examples/games/rpg_demo.ludic b/examples/games/rpg_demo.ludic index 45c9a5ab..5fc94fe4 100644 --- a/examples/games/rpg_demo.ludic +++ b/examples/games/rpg_demo.ludic @@ -38,14 +38,16 @@ program RpgDemo { @OnSpawn(Cfg) handler CI { } # a locked-door rule: veto a rightward grid step while `locked` is set (lever 4). - var locked: int = 0 - @On(MoveRequested) handler LockDoor { if (locked == 1) and (dx == 1) { cancel } } - var entered: int = 0 - @On(TileEntered) handler CountEnter { entered += 1 } - var talked: int = 0 - @On(Interacted) handler OnTalk { if target >= 0 { talked += 1 } } - var quest_done: int = 0 - @On(QuestCompleted) handler OnQuestDone { quest_done += 1 } + state RpgdemoState { + locked: int = 0 + entered: int = 0 + talked: int = 0 + quest_done: int = 0 + } + @On(MoveRequested) handler LockDoor(rpgdemo_st: RpgdemoState) { if (rpgdemo_st.locked == 1) and (dx == 1) { cancel } } + @On(TileEntered) handler CountEnter(rpgdemo_st: mut RpgdemoState) { rpgdemo_st.entered += 1 } + @On(Interacted) handler OnTalk(rpgdemo_st: mut RpgdemoState) { if target >= 0 { rpgdemo_st.talked += 1 } } + @On(QuestCompleted) handler OnQuestDone(rpgdemo_st: mut RpgdemoState) { rpgdemo_st.quest_done += 1 } function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } } function bi(b: bool) -> int { if b { return 1 }; return 0 } @@ -53,7 +55,7 @@ program RpgDemo { function py(e: int) -> int { let P = World.prop_id("Position"); return World.get(e, P, World.field_id(P, "y")) } function hp(e: int) -> int { let P = World.prop_id("Stats"); return World.get(e, P, World.field_id(P, "hp")) } - entry { + entry (rpgdemo_st: mut RpgdemoState) { let PM = World.prop_id("Mover") # a 6x3 tile world; a wall glyph at cell (3,1) @@ -71,7 +73,7 @@ program RpgDemo { # move down into the open cell (16,32) Mover.move(h, 0, 1); tick_n(1); Mover.move(h, 0, 0); tick_n(1) print(bi((px(h) == 16) and (py(h) == 32))) # 1 — stepped one cell down - print(bi(entered >= 1)) # 1 — TileEntered fired + print(bi(rpgdemo_st.entered >= 1)) # 1 — TileEntered fired # try to walk into the wall at cell (3,1)=(48,16): from (32,16) moving right blocked World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "x"), 32) @@ -81,17 +83,17 @@ program RpgDemo { # MoveRequested veto: a locked door forbids the rightward step (open cell now) World.set(h, World.prop_id("Position"), World.field_id(World.prop_id("Position"), "y"), 32) # row 2 open - locked = 1 + rpgdemo_st.locked = 1 Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0) print(bi(px(h) == 32)) # 1 — vetoed, no move - locked = 0 + rpgdemo_st.locked = 0 Mover.move(h, 1, 0); tick_n(1); Mover.move(h, 0, 0) print(bi(px(h) == 48)) # 1 — now allowed # interact: an NPC one cell ahead (facing right after the last move) spawn Npc { Position { x: 64, y: 32 } } print(bi(Mover.interact(h) >= 0)) # 1 — found the NPC ahead - print(bi(talked == 1)) # 1 — Interacted fired + print(bi(rpgdemo_st.talked == 1)) # 1 — Interacted fired # ================= Module B: inventory + equipment ================= Item.def("herb", 99, 1, 2) @@ -120,7 +122,7 @@ program RpgDemo { Quest.start("hunt") Quest.notify(0, 0, 1); Quest.notify(0, 0, 1); Quest.notify(0, 0, 1) print(bi(Quest.state_of("hunt") == 3)) # 1 — auto-completed - print(bi(quest_done == 1)) # 1 — QuestCompleted fired + print(bi(rpgdemo_st.quest_done == 1)) # 1 — QuestCompleted fired # ================= Module E: dialog ================= let dn0 = Dialog.node("elder", "hello") diff --git a/examples/games/shooter_demo.ludic b/examples/games/shooter_demo.ludic index 297133a6..a4838c43 100644 --- a/examples/games/shooter_demo.ludic +++ b/examples/games/shooter_demo.ludic @@ -30,19 +30,21 @@ program ShooterDemo { @OnSpawn(Mob) handler MInit { } @OnSpawn(Nest) handler NInit { } - var spawn_reqs: int = 0 - @On(SpawnRequested) handler OnSpawnReq { spawn_reqs += 1 } - var waves_cleared: int = 0 - @On(WaveCleared) handler OnWaveClear { waves_cleared += 1 } + state ShooterdemoState { + spawn_reqs: int = 0 + waves_cleared: int = 0 + spawned: int = 0 + hits: int = 0 + deaths: int = 0 + } + @On(SpawnRequested) handler OnSpawnReq(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.spawn_reqs += 1 } + @On(WaveCleared) handler OnWaveClear(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.waves_cleared += 1 } - var spawned: int = 0 - @On(ProjectileSpawned) handler CountSpawn { spawned += 1 } - var hits: int = 0 - @On(ProjectileHit) handler CountHit { hits += 1 } + @On(ProjectileSpawned) handler CountSpawn(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.spawned += 1 } + @On(ProjectileHit) handler CountHit(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.hits += 1 } # reap the dead (the realistic policy: a Died body leaves the world) so stale # corpses do not sit in the faction/targeting scans. - var deaths: int = 0 - @On(Died) handler Reap { deaths += 1; despawn e } + @On(Died) handler Reap(shooterdemo_st: mut ShooterdemoState) { shooterdemo_st.deaths += 1; despawn e } function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } } function bi(b: bool) -> int { if b { return 1 }; return 0 } @@ -72,7 +74,7 @@ program ShooterDemo { return -1 } - entry { + entry (shooterdemo_st: ShooterdemoState) { let PT = World.prop_id("TopDown") let PB = World.prop_id("Body") let PP = World.prop_id("Position") @@ -110,7 +112,7 @@ program ShooterDemo { TopDown.fire(g, 0) let hp_before = hp_of(mob) tick_n(40) # bullet flies ~120px and hits - print(bi(spawned >= 1)) # 1 — a projectile spawned + print(bi(shooterdemo_st.spawned >= 1)) # 1 — a projectile spawned print(bi(hp_of(mob) < hp_before)) # 1 — enemy took damage # ---- C. friendly fire is ignored (same faction) ------------------------ @@ -125,29 +127,29 @@ program ShooterDemo { print(bi(hp_of(ally) == ally_hp)) # 1 — ally unharmed (faction) # ---- D. shotgun spread emits multiple pellets in one shot --------------- - let s0 = spawned + let s0 = shooterdemo_st.spawned Weapon.equip(g, "shotgun") TopDown.aim(g, 0) TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0) - print(bi(spawned - s0 == 5)) # 1 — 5 pellets + print(bi(shooterdemo_st.spawned - s0 == 5)) # 1 — 5 pellets # ---- E. a fresh enemy is eventually killed (Combat -> Died -> reaped) -- - let d0 = deaths + let d0 = shooterdemo_st.deaths Weapon.equip(g, "rifle") TopDown.aim(g, 0) var f = 0 - while (deaths == d0) and (f < 400) { + while (shooterdemo_st.deaths == d0) and (f < 400) { TopDown.fire(g, 1); tick_fixed(); TopDown.fire(g, 0); tick_fixed() f += 1 } - print(bi(deaths == d0 + 1)) # 1 — enemy destroyed (Died fired) + print(bi(shooterdemo_st.deaths == d0 + 1)) # 1 — enemy destroyed (Died fired) # ---- F. ring pattern (bullet-hell) emits N bullets around 360 ---------- - let r0 = spawned + let r0 = shooterdemo_st.spawned let ring = Weapon.def("nova", 30, 3, 6, 0, 8, 2) # pattern 2 = ring, 8 pellets Weapon.equip(g, "nova") TopDown.fire(g, 1); tick_n(1); TopDown.fire(g, 0) - print(bi(spawned - r0 == 8)) # 1 — 8-way ring + print(bi(shooterdemo_st.spawned - r0 == 8)) # 1 — 8-way ring # ---- G. homing shot curves onto an off-axis enemy ---------------------- let seek = Weapon.def("seeker", 30, 40, 6, 0, 1, 0) @@ -166,8 +168,8 @@ program ShooterDemo { # ---- H. wave spawner: budgeted, throttled SpawnRequested + WaveCleared -- spawn Nest { Position { x: 300, y: 300 }, Spawner { rate: 3, budget: 4 } } tick_n(20) # 4 spawns at 3-frame spacing, then cleared - print(bi(spawn_reqs == 4)) # 1 — exactly budget requests - print(bi(waves_cleared == 1)) # 1 — wave cleared once + print(bi(shooterdemo_st.spawn_reqs == 4)) # 1 — exactly budget requests + print(bi(shooterdemo_st.waves_cleared == 1)) # 1 — wave cleared once quit() } diff --git a/examples/games/snake.ludic b/examples/games/snake.ludic index 2cc24601..47b18fb4 100644 --- a/examples/games/snake.ludic +++ b/examples/games/snake.ludic @@ -24,17 +24,19 @@ program Snake { const MODE_OVER: int = 1 # ---- the game's whole state, named ---------------------------------------- - var direction: int = DIR_RIGHT - var length: int = 3 - var tick: int = 0 - var food_x: int = 14 - var food_y: int = 7 - var mode: int = MODE_PLAY - var score: int = 0 - var ate: int = 0 # did the head reach the food this step? - var tail_x: int = 0 # where the tail sat before it moved (grow here) - var tail_y: int = 0 - var acked: int = 0 # game-over key must be released before restart + state SnakeState { + direction: int = DIR_RIGHT + length: int = 3 + tick: int = 0 + food_x: int = 14 + food_y: int = 7 + mode: int = MODE_PLAY + score: int = 0 + ate: int = 0 # did the head reach the food this step? + tail_x: int = 0 # where the tail sat before it moved (grow here) + tail_y: int = 0 + acked: int = 0 # game-over key must be released before restart + } # ---- custom shades this game uses (values the named palette doesn't carry) - const SHADE_BG: int = 0x0d1020 # backdrop behind the board @@ -46,48 +48,48 @@ program Snake { const SHADE_SCORE: int = 0xffe060 const SHADE_OVER: int = 0xff5555 - function reset(unused: int) -> int { + function reset(snake_st: mut SnakeState, unused: int) -> int { for (p, s) in query [Pos, Seg] { despawn self() } spawn S0 { Seg { order: 0 }; Pos { x: 10, y: 7 } } spawn S1 { Seg { order: 1 }; Pos { x: 9, y: 7 } } spawn S2 { Seg { order: 2 }; Pos { x: 8, y: 7 } } - direction = DIR_RIGHT - length = 3 - tick = 0 - mode = MODE_PLAY - score = 0 - food_x = 14 - food_y = 7 + snake_st.direction = DIR_RIGHT + snake_st.length = 3 + snake_st.tick = 0 + snake_st.mode = MODE_PLAY + snake_st.score = 0 + snake_st.food_x = 14 + snake_st.food_y = 7 return 0 } - handler Boot phase Start { reset(0) } + handler Boot(snake_st: mut SnakeState) phase Start { reset(snake_st, 0) } - handler Input phase Input { + handler Input(snake_st: mut SnakeState) phase Input { let k = Input.key() - if mode == MODE_PLAY { - if k == 'w' { if direction != DIR_DOWN { direction = DIR_UP } } - if k == 's' { if direction != DIR_UP { direction = DIR_DOWN } } - if k == 'a' { if direction != DIR_RIGHT { direction = DIR_LEFT } } - if k == 'd' { if direction != DIR_LEFT { direction = DIR_RIGHT } } + if snake_st.mode == MODE_PLAY { + if k == 'w' { if snake_st.direction != DIR_DOWN { snake_st.direction = DIR_UP } } + if k == 's' { if snake_st.direction != DIR_UP { snake_st.direction = DIR_DOWN } } + if k == 'a' { if snake_st.direction != DIR_RIGHT { snake_st.direction = DIR_LEFT } } + if k == 'd' { if snake_st.direction != DIR_LEFT { snake_st.direction = DIR_RIGHT } } } - if mode == MODE_OVER { - if k == 0 { acked = 1 } - if k != 0 { if acked == 1 { reset(0) } } + if snake_st.mode == MODE_OVER { + if k == 0 { snake_st.acked = 1 } + if k != 0 { if snake_st.acked == 1 { reset(snake_st, 0) } } } } - handler Move phase Update { - if mode == MODE_PLAY { - tick += 1 - if tick >= 6 { - tick = 0 + handler Move(snake_st: mut SnakeState) phase Update { + if snake_st.mode == MODE_PLAY { + snake_st.tick += 1 + if snake_st.tick >= 6 { + snake_st.tick = 0 # remember the tail cell, then drag every segment onto the one ahead of it for (pt, stg) in query [Pos, Seg] { - if stg.order == length - 1 { tail_x = pt.x; tail_y = pt.y } + if stg.order == snake_st.length - 1 { snake_st.tail_x = pt.x; snake_st.tail_y = pt.y } } - for step in 0 .. length { - let i = length - 1 - step + for step in 0 .. snake_st.length { + let i = snake_st.length - 1 - step if i >= 1 { for (pa, sa) in query [Pos, Seg] { if sa.order == i { @@ -99,18 +101,18 @@ program Snake { } } # move the head, check the walls, check the food - ate = 0 + snake_st.ate = 0 for (ph, sh) in query [Pos, Seg] { if sh.order == 0 { - if direction == DIR_UP { ph.y -= 1 } - if direction == DIR_DOWN { ph.y += 1 } - if direction == DIR_LEFT { ph.x -= 1 } - if direction == DIR_RIGHT { ph.x += 1 } - if ph.x < 0 { mode = MODE_OVER } - if ph.x >= GRID_W { mode = MODE_OVER } - if ph.y < 0 { mode = MODE_OVER } - if ph.y >= GRID_H { mode = MODE_OVER } - if ph.x == food_x { if ph.y == food_y { ate = 1 } } + if snake_st.direction == DIR_UP { ph.y -= 1 } + if snake_st.direction == DIR_DOWN { ph.y += 1 } + if snake_st.direction == DIR_LEFT { ph.x -= 1 } + if snake_st.direction == DIR_RIGHT { ph.x += 1 } + if ph.x < 0 { snake_st.mode = MODE_OVER } + if ph.x >= GRID_W { snake_st.mode = MODE_OVER } + if ph.y < 0 { snake_st.mode = MODE_OVER } + if ph.y >= GRID_H { snake_st.mode = MODE_OVER } + if ph.x == snake_st.food_x { if ph.y == snake_st.food_y { snake_st.ate = 1 } } } } # self-collision: head onto any body segment @@ -118,23 +120,23 @@ program Snake { if sh.order == 0 { for (pb, sb) in query [Pos, Seg] { if sb.order > 0 { - if pb.x == ph.x { if pb.y == ph.y { mode = MODE_OVER } } + if pb.x == ph.x { if pb.y == ph.y { snake_st.mode = MODE_OVER } } } } } } - if ate == 1 { - score += 1 - spawn Body { Seg { order: length }; Pos { x: tail_x, y: tail_y } } - length += 1 - food_x = Random.range(low: 0, high: GRID_W - 1) - food_y = Random.range(low: 0, high: GRID_H - 1) + if snake_st.ate == 1 { + snake_st.score += 1 + spawn Body { Seg { order: snake_st.length }; Pos { x: snake_st.tail_x, y: snake_st.tail_y } } + snake_st.length += 1 + snake_st.food_x = Random.range(low: 0, high: GRID_W - 1) + snake_st.food_y = Random.range(low: 0, high: GRID_H - 1) } } } } - handler Draw phase Render { + handler Draw(snake_st: SnakeState) phase Render { Screen.clear(SHADE_BG) # checkerboard field for gy in 0 .. GRID_H { @@ -145,7 +147,7 @@ program Snake { } } # food, inset a few pixels inside its tile - Screen.fill_rectangle(x: food_x * TILE + 3, y: food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD) + Screen.fill_rectangle(x: snake_st.food_x * TILE + 3, y: snake_st.food_y * TILE + 3, width: TILE - 6, height: TILE - 6, color: SHADE_FOOD) # every snake segment, straight from the ECS for (p, s) in query [Pos, Seg] { var seg_color = SHADE_BODY @@ -153,8 +155,8 @@ program Snake { Screen.fill_rectangle(x: p.x * TILE + 1, y: p.y * TILE + 1, width: TILE - 2, height: TILE - 2, color: seg_color) } Screen.draw_text(x: 6, y: 4, text: "SCORE", color: Color.White, scale: 1) - Screen.draw_number(x: 52, y: 4, value: score, color: SHADE_SCORE, scale: 1) - if mode == MODE_OVER { + Screen.draw_number(x: 52, y: 4, value: snake_st.score, color: SHADE_SCORE, scale: 1) + if snake_st.mode == MODE_OVER { Screen.draw_text(x: 112, y: 104, text: "GAME OVER", color: SHADE_OVER, scale: 2) Screen.draw_text(x: 84, y: 140, text: "PRESS ANY KEY", color: Color.White, scale: 1) } diff --git a/examples/lang/floats.ludic b/examples/lang/floats.ludic index 04a6c86c..67f6a006 100644 --- a/examples/lang/floats.ludic +++ b/examples/lang/floats.ludic @@ -3,13 +3,15 @@ # Running it prints: 1 2 3 4 5 6 7 8 9 10 11 12 program Floats { const HALF: float = 0.5 - var g_gravity: float = -9.81 + state FloatsState { + g_gravity: float = -9.81 + } property Body { x: float = 1.25, vy: float, mass: double = 70.5 } function lerp(a: float, b: float, t: float) -> float { return a + (b - a) * t } - entry { + entry (floats_st: FloatsState) { let a: float = 0.1 if a * 3 > 0.29 and a * 3 < 0.31 { print(1) } # the int 3 promotes if lerp(0, 10, 0.25) == 2.5 { print(2) } # ints and literals as floats @@ -18,7 +20,7 @@ program Floats { if string(a + 0.2) == "0.3" { print(4) } # shortest text that reads back let b = new Body - b.vy = b.vy + g_gravity * HALF + b.vy = b.vy + floats_st.g_gravity * HALF if b.vy < -4.9 and b.vy > -4.91 and b.mass == 70.5 { print(5) } let v = floats(3) diff --git a/examples/lang/long_literals.ludic b/examples/lang/long_literals.ludic index 73e106fe..cfe4285c 100644 --- a/examples/lang/long_literals.ludic +++ b/examples/lang/long_literals.ludic @@ -5,12 +5,14 @@ # Running it prints: 4294967295 3988292384 -4294967296 1099511627776 -3750763034362895579 -1 1 program LongLiterals { const MASK: long = 4294967295 - var poly: long = 3988292384 + state LongliteralsState { + poly: long = 3988292384 + } function neg() -> long { return -4294967296 } - entry { + entry (longliterals_st: LongliteralsState) { let big = 0x10000000000 let fnv = 0xcbf29ce484222325 let small = 0xFFFFFFFF - print(`{MASK} {poly} {neg()} {big} {fnv} {small} {MASK > long(2147483647)}`) + print(`{MASK} {longliterals_st.poly} {neg()} {big} {fnv} {small} {MASK > long(2147483647)}`) } } diff --git a/examples/lang/scenes.ludic b/examples/lang/scenes.ludic index c23c7073..9089fc3f 100644 --- a/examples/lang/scenes.ludic +++ b/examples/lang/scenes.ludic @@ -15,30 +15,32 @@ # bin/ludic-dev game-build bin/ludicc examples/lang/scenes.ludic /tmp/scenes # printf 'aaaa' | /tmp/scenes program SceneDemo { - var counter: int = 0 + state ScenedemoState { + counter: int = 0 + } - handler Boot phase Start { counter = 0; print(1000) } + handler Boot(scenedemo_st: mut ScenedemoState) phase Start { scenedemo_st.counter = 0; print(1000) } scene Title start { on enter { print(1) } on exit { print(2) } layer Main { - handler Tick phase Update { - counter += 1 - print(100 + counter) - if counter >= 2 { become Play } + handler Tick(scenedemo_st: mut ScenedemoState) phase Update { + scenedemo_st.counter += 1 + print(100 + scenedemo_st.counter) + if scenedemo_st.counter >= 2 { become Play } } } } scene Play { - on enter { print(3); counter = 0 } + on enter (scenedemo_st: mut ScenedemoState) { print(3); scenedemo_st.counter = 0 } on exit { print(4) } layer World { - handler Step phase Update { - counter += 1 - print(200 + counter) - if counter >= 2 { quit() } + handler Step(scenedemo_st: mut ScenedemoState) phase Update { + scenedemo_st.counter += 1 + print(200 + scenedemo_st.counter) + if scenedemo_st.counter >= 2 { quit() } } } layer Hud { diff --git a/examples/library/combat_kit.ludic b/examples/library/combat_kit.ludic index 4afce2ce..030a4764 100644 --- a/examples/library/combat_kit.ludic +++ b/examples/library/combat_kit.ludic @@ -12,13 +12,15 @@ program CombatKit { model Hero { Position, Body, Collider, TopDown, Weapon, Faction, Stats, Dash, Melee } model Creature { Position, Body, Collider, Faction, Stats, Foe } model WorldRules { Solids } - var frames: int = 0 - var hero: int = -1 + state CombatkitState { + frames: int = 0 + hero: int = -1 + } @On(MeleeHit) handler H { print(5000 + target) } @On(Crit) handler C { print(6000) } - handler Tick phase Update { - frames += 1 - if frames == 1 { + handler Tick(combatkit_st: mut CombatkitState) phase Update { + combatkit_st.frames += 1 + if combatkit_st.frames == 1 { Map.size(20, 15) Dungeon.arena(Dungeon.random_style(), '#', '.') Dungeon.set_exit(Side.North, 'D') @@ -28,19 +30,19 @@ program CombatKit { print(e.x * 1000 + e.y) # 153196 print(Dungeon.opposite(Side.West) * 10 + Dungeon.at_edge(IVec2.make(0, 3))) # 31 Weapon.def(name: "p", fire_rate: 10, damage: 10, speed: 8, spread: 0, pellets: 1, pattern: 0) - hero = spawn Hero { Position { x: 100, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, TopDown { move_speed: 4, aim_mode: AimMode.Manual, aim_angle: 0 }, Faction { id: 1 }, + combatkit_st.hero = spawn Hero { Position { x: 100, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, TopDown { move_speed: 4, aim_mode: AimMode.Manual, aim_angle: 0 }, Faction { id: 1 }, Stats { hp: 100, max_hp: 100 }, Dash { frames: 3, speed: 9, cooldown_frames: 5 }, Melee { range: 30, damage: 7, knockback: 4 } } - Stats.add(hero, StatKind.CritPct, 100) - Stats.add(hero, StatKind.DamagePct, 50) - let c = Collider.center(hero); print(c.x * 1000 + c.y) # 107110 + Stats.add(combatkit_st.hero, StatKind.CritPct, 100) + Stats.add(combatkit_st.hero, StatKind.DamagePct, 50) + let c = Collider.center(combatkit_st.hero); print(c.x * 1000 + c.y) # 107110 spawn Creature { Position { x: 120, y: 100 }, Collider { w: 14, h: 20 }, Body { policy: 1 }, Faction { id: 2 }, Stats { hp: 100, max_hp: 100, thorns: 3 }, Foe { n: 1 } } Faction.set(1, 2, 2) - print(Melee.swing(hero)) # 1 hit (after 6000 and 5002) + print(Melee.swing(combatkit_st.hero)) # 1 hit (after 6000 and 5002) for (s, f) in query [Stats, Foe] { print(s.hp) } # 80: 7 * 150% doubled by the crit - print(Stats.of(hero).hp) # 97: thorns - print(Dash.start(hero, 0, 0) * 10 + Dash.active(hero)) # 11 + print(Stats.of(combatkit_st.hero).hp) # 97: thorns + print(Dash.start(combatkit_st.hero, 0, 0) * 10 + Dash.active(combatkit_st.hero)) # 11 print(Prefs.max("best", 5) * 10 + Prefs.max("best", 3)) # 10 } - if frames == 7 { print(700 + Dash.active(hero)); print(Position.of(hero).x); quit() } # 700 110 + if combatkit_st.frames == 7 { print(700 + Dash.active(combatkit_st.hero)); print(Position.of(combatkit_st.hero).x); quit() } # 700 110 } } diff --git a/examples/library/component_access.ludic b/examples/library/component_access.ludic index e07a302d..15498449 100644 --- a/examples/library/component_access.ludic +++ b/examples/library/component_access.ludic @@ -10,30 +10,32 @@ program ComponentAccess { model Player { Position, Hero } model Thing { Position, Tag } enum HeroState { Idle, Rolling, Swinging } - var hero_state: HeroState = HeroState.Rolling - var player: int = -1 - var frames: int = 0 - function step() -> void { - machine hero_state { + state ComponentaccessState { + hero_state: HeroState = HeroState.Rolling + player: int = -1 + frames: int = 0 + } + function step(componentaccess_st: mut ComponentaccessState) -> void { + machine componentaccess_st.hero_state { state Idle { print(100); become Swinging } state Rolling { print(101); become Idle } state Swinging { print(102); become Rolling } } } - handler Tick phase Update { - frames += 1 - if frames == 1 { - player = spawn Player { Position { x: 3, y: 4 }, Hero { roll_frames: 9, flash: 3 } } + handler Tick(componentaccess_st: mut ComponentaccessState) phase Update { + componentaccess_st.frames += 1 + if componentaccess_st.frames == 1 { + componentaccess_st.player = spawn Player { Position { x: 3, y: 4 }, Hero { roll_frames: 9, flash: 3 } } spawn Thing { Position { x: 30, y: 40 }, Tag { n: 7 } } - print(Hero.of(player).roll_frames) # 9 - Hero.of(player).iframes = 20 - Hero.of(player).iframes -= 5 - print(Hero.of(player).iframes) # 15 - let hero = Hero.of(player) + print(Hero.of(componentaccess_st.player).roll_frames) # 9 + Hero.of(componentaccess_st.player).iframes = 20 + Hero.of(componentaccess_st.player).iframes -= 5 + print(Hero.of(componentaccess_st.player).iframes) # 15 + let hero = Hero.of(componentaccess_st.player) hero.roll_frames += 1 - print(Hero.of(player).roll_frames) # 10 - print(Position.of(player).x + Position.of(1).x) # 33 - print(Hero.has(player)) # 1 + print(Hero.of(componentaccess_st.player).roll_frames) # 10 + print(Position.of(componentaccess_st.player).x + Position.of(1).x) # 33 + print(Hero.has(componentaccess_st.player)) # 1 print(Hero.has(1)) # 0 print(Hero.has(-1)) # 0 print(Tag.has(1)) # 1 @@ -42,13 +44,13 @@ program ComponentAccess { print(Position.count()) # 1: the player spawn Thing { Position { x: 1 } } } - if frames == 2 { - step(); step(); step() # 101 100 102 - print(hero_state) # 1 + if componentaccess_st.frames == 2 { + step(componentaccess_st); step(componentaccess_st); step(componentaccess_st) # 101 100 102 + print(componentaccess_st.hero_state) # 1 print(Position.count()) # 2: the player and the thing spawned after } - if frames == 3 { - print(Hero.of(player).flash) # ticked twice: 1 + if componentaccess_st.frames == 3 { + print(Hero.of(componentaccess_st.player).flash) # ticked twice: 1 Position.despawn_all() print(Position.count()) # 0 quit() diff --git a/examples/library/gameplay_foundation.ludic b/examples/library/gameplay_foundation.ludic index 3d840b19..1e3b32c8 100644 --- a/examples/library/gameplay_foundation.ludic +++ b/examples/library/gameplay_foundation.ludic @@ -17,19 +17,21 @@ program GameplayFoundation { # a game-supplied policy: while "enraged" is set, halve incoming damage by # rewriting the pending amount (lever 6, the mutable hook), and hard-veto any # hit over 100 (lever 4, cancel). Both are just listeners — no engine fork. - var enraged: int = 0 - @On(DamageAboutToApply) handler Ward { + state GameplayfoundationState { + enraged: int = 0 + deaths: int = 0 + } + @On(DamageAboutToApply) handler Ward(gameplayfoundation_st: GameplayfoundationState) { if amount > 100 { cancel } - if enraged == 1 { Combat.set_amount(amount / 2) } + if gameplayfoundation_st.enraged == 1 { Combat.set_amount(amount / 2) } } - var deaths: int = 0 - @On(Died) handler Tally { deaths += 1 } + @On(Died) handler Tally(gameplayfoundation_st: mut GameplayfoundationState) { gameplayfoundation_st.deaths += 1 } function tick_n(n: int) -> void { var i = 0; while i < n { tick_fixed(); i += 1 } } function bi(b: bool) -> int { if b { return 1 }; return 0 } - entry { + entry (gameplayfoundation_st: mut GameplayfoundationState) { let PS = World.prop_id("Stats") let fhp = World.field_id(PS, "hp") let fatk = World.field_id(PS, "atk") @@ -65,9 +67,9 @@ program GameplayFoundation { print(World.get(t, PS, fhp)) # 85 # mutable hook: enrage halves the next hit ((20 - 5) -> 7) - enraged = 1 + gameplayfoundation_st.enraged = 1 print(Combat.damage(t, a, 20)) # 7 - enraged = 0 + gameplayfoundation_st.enraged = 0 # veto hook: an over-100 hit is cancelled (0 applied) print(bi(Combat.damage(t, a, 200) == 0)) # 1 @@ -75,7 +77,7 @@ program GameplayFoundation { # finish it off with sub-100 hits so the veto never blocks the kill var guard = 0 while (World.get(t, PS, fhp) > 0) and (guard < 100) { Combat.damage(t, a, 90); guard += 1 } - print(deaths) # 1 + print(gameplayfoundation_st.deaths) # 1 quit() } diff --git a/examples/library/input_auto.ludic b/examples/library/input_auto.ludic index 74bcaca2..5c1ad727 100644 --- a/examples/library/input_auto.ludic +++ b/examples/library/input_auto.ludic @@ -12,9 +12,11 @@ program InputAuto { property Tag { n: int = 0 } model Player { Tag } - var hits: int = 0 + state InputautoState { + hits: int = 0 + } @OnStart handler Boot { Input.action("go", 'l') } # 'l' == 108 - handler Sense phase Update { if Input.active("go") { hits += 1 } } - @OnQuit handler Report { print(hits) } + handler Sense(inputauto_st: mut InputautoState) phase Update { if Input.active("go") { inputauto_st.hits += 1 } } + @OnQuit handler Report(inputauto_st: InputautoState) { print(inputauto_st.hits) } } diff --git a/examples/library/managers.ludic b/examples/library/managers.ludic index 5012d49e..7df9f023 100644 --- a/examples/library/managers.ludic +++ b/examples/library/managers.ludic @@ -8,10 +8,12 @@ program Managers { property Tag { n: int = 0 } model Thing { Position, Body, Sprite, Tag, TopDown } model WorldRules { Solids } - var frames: int = 0 - handler Tick phase Update { - frames += 1 - if frames == 1 { + state ManagersState { + frames: int = 0 + } + handler Tick(managers_st: mut ManagersState) phase Update { + managers_st.frames += 1 + if managers_st.frames == 1 { Map.size(10, 8) Map.fill(glyph: '.') Map.border(glyph: '#') @@ -36,7 +38,7 @@ program Managers { Fx.number(x: 20, y: 20, value: 42, color: 0xffff00) spawn Thing { Position { x: 20, y: 20 }, Sprite { id: 5, move_id: 9, face: 1, flash: 3, blink: 6 }, TopDown { want_x: -1 } } } - if frames == 3 { print(Map.is_solid(x: 0, y: 0) * 10 + Map.is_solid_at(x: 40, y: 40)) } # 10 - if frames == 5 { for (s, t) in query [Sprite, Tag] { print(s.flip * 100 + s.flash * 10 + s.blink) }; quit() } # 102 + if managers_st.frames == 3 { print(Map.is_solid(x: 0, y: 0) * 10 + Map.is_solid_at(x: 40, y: 40)) } # 10 + if managers_st.frames == 5 { for (s, t) in query [Sprite, Tag] { print(s.flip * 100 + s.flash * 10 + s.blink) }; quit() } # 102 } } diff --git a/examples/library/prefabs.ludic b/examples/library/prefabs.ludic index 3b518b06..6729a1eb 100644 --- a/examples/library/prefabs.ludic +++ b/examples/library/prefabs.ludic @@ -8,9 +8,11 @@ program Prefabs { model Creature { Position, Enemy } prefab Foe: Creature { Enemy { team: 2, hp: 1 }, Position { y: 9 } } prefab Grunt: Foe { Enemy { kind: 1, hp: 30 } } - var last: int = -1 - @OnSpawn(Creature) handler Remember { last = self() } - entry { + state PrefabsState { + last: int = -1 + } + @OnSpawn(Creature) handler Remember(prefabs_st: mut PrefabsState) { prefabs_st.last = self() } + entry (prefabs_st: PrefabsState) { let g = spawn Grunt { Position { x: 5 } } # Foe's presets, Grunt's, then this print(Enemy.of(g).team * 100000 + Enemy.of(g).hp * 1000 + Enemy.of(g).kind * 100 + Position.of(g).x * 10 + Position.of(g).y) let n = Prefab.spawn(name: "Grunt") @@ -20,6 +22,6 @@ program Prefabs { print(Position.of(placed).x) print(Position.of(placed).y) print(Prefab.spawn(name: "Nope")) # no such prefab - print(last) # self() in the hook: the last spawn + print(prefabs_st.last) # self() in the hook: the last spawn } } diff --git a/examples/library/scene_menus.ludic b/examples/library/scene_menus.ludic index c3978392..94c3a635 100644 --- a/examples/library/scene_menus.ludic +++ b/examples/library/scene_menus.ludic @@ -6,18 +6,20 @@ program SceneMenus { import "ludic.core/components.ludic" property Pos { x: int = 0 } model Thing { Pos } - var menu_font: int = 0 - var frames: int = 0 + state ScenemenusState { + menu_font: int = 0 + frames: int = 0 + } ui Menu { panel id: Root w: 100 pad: 4 bg: Color.Gunmetal border: Color.SlateGray align: center { - button id: Go text: "Go" font: menu_font size: 16 w: 80 goto: Play + button id: Go text: "Go" font: ScenemenusState.menu_font size: 16 w: 80 goto: Play } } scene Splash start lasts 1 then Title { on enter { print(100) } } scene Title shows Menu { on enter { print(200) } on exit { print(202) } - layer L { handler Tick phase Update { frames += 1; if frames == 1 { print(201); emit UiClicked(id: UI_Go) } } } + layer L { handler Tick(scenemenus_st: mut ScenemenusState) phase Update { scenemenus_st.frames += 1; if scenemenus_st.frames == 1 { print(201); emit UiClicked(id: UI_Go) } } } } @On(UiClicked) handler Report { print(id) } # UI_Go is widget 1 (the root panel is 0) scene Play { diff --git a/examples/library/sprite_atlas.ludic b/examples/library/sprite_atlas.ludic index 1af47be2..57b6b792 100644 --- a/examples/library/sprite_atlas.ludic +++ b/examples/library/sprite_atlas.ludic @@ -10,23 +10,25 @@ program SpriteAtlas { import "ludic.core/components.ludic" - var TALL: int = 0 + state SpriteatlasState { + TALL: int = 0 + } function bi(b: bool) -> int { if b { return 1 }; return 0 } - @OnStart handler Boot { + @OnStart handler Boot(spriteatlas_st: mut SpriteatlasState) { let sheet = Sprite.sheet("assets/kenney/tiny-dungeon/Tilemap/tilemap_packed.png", 16, 16) - TALL = Sprite.cell_span(sheet, 0, 0, 1, 2) # a 16x32 sprite: two cells tall - spawn Tall { Position { x: 40, y: 40 }, Sprite { id: TALL, atlas: 1 } } - spawn Tinted { Position { x: 100, y: 40 }, Sprite { id: TALL, atlas: 1, tint: 0xff0000 } } - spawn Table { Position { x: 160, y: 40 }, Sprite { id: TALL } } # atlas: 0 -> the (empty) 16x16 table + spriteatlas_st.TALL = Sprite.cell_span(sheet, 0, 0, 1, 2) # a 16x32 sprite: two cells tall + spawn Tall { Position { x: 40, y: 40 }, Sprite { id: spriteatlas_st.TALL, atlas: 1 } } + spawn Tinted { Position { x: 100, y: 40 }, Sprite { id: spriteatlas_st.TALL, atlas: 1, tint: 0xff0000 } } + spawn Table { Position { x: 160, y: 40 }, Sprite { id: spriteatlas_st.TALL } } # atlas: 0 -> the (empty) 16x16 table } # An empty Render handler: the engine sprite system draws the Sprite entities. handler Draw phase Render { } - @OnQuit handler Report { - print(Sprite.height(TALL)) # 32 — the span is two cells tall + @OnQuit handler Report(spriteatlas_st: SpriteatlasState) { + print(Sprite.height(spriteatlas_st.TALL)) # 32 — the span is two cells tall var top = 0 for yy in 40 .. 56 { for xx in 40 .. 56 { if Screen.pixel(xx, yy) != 0 { top = 1 } } } diff --git a/examples/library/sprite_render.ludic b/examples/library/sprite_render.ludic index 48139610..bac6a39e 100644 --- a/examples/library/sprite_render.ludic +++ b/examples/library/sprite_render.ludic @@ -9,12 +9,14 @@ program SpriteRender { import "ludic.core/components.ludic" - var HERO: int = 0 + state SpriterenderState { + HERO: int = 0 + } - @OnStart handler Boot { - HERO = png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png") - spawn Shown { Position { x: 40, y: 40 }, Sprite { id: HERO } } - spawn Hidden { Position { x: 100, y: 40 }, Sprite { id: HERO, hidden: 1 } } + @OnStart handler Boot(spriterender_st: mut SpriterenderState) { + spriterender_st.HERO = png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png") + spawn Shown { Position { x: 40, y: 40 }, Sprite { id: spriterender_st.HERO } } + spawn Hidden { Position { x: 100, y: 40 }, Sprite { id: spriterender_st.HERO, hidden: 1 } } } # An empty Render handler: the engine sprite system draws the Sprite entities. diff --git a/examples/library/threads.ludic b/examples/library/threads.ludic index 91ad7175..24327230 100644 --- a/examples/library/threads.ludic +++ b/examples/library/threads.ludic @@ -6,25 +6,27 @@ program Threads { const N: int = 20000 - var calls: int = 0 # an atomic handle, made on the main thread before any work - var lock: int = 0 # a mutex handle - var total: long = 0 # guarded by `lock` + state ThreadsState { + calls: int = 0 # an atomic handle, made on the main thread before any work + lock: int = 0 # a mutex handle + total: long = 0 # guarded by `lock` + } # out[i] = i * i: every index written exactly once, by whichever thread claimed it function square(i: int, out: words) -> void { out[i] = i * i } # count the call atomically, and add i to a shared total under the mutex - function tally(i: int, ctx: pointer) -> void { - Sync.add(calls, 1) - Sync.lock(lock) - total = total + i - Sync.unlock(lock) + function tally(threads_st: mut ThreadsState, i: int, ctx: pointer) -> void { + Sync.add(threads_st.calls, 1) + Sync.lock(threads_st.lock) + threads_st.total = threads_st.total + i + Sync.unlock(threads_st.lock) } # remember which calls ran on a pool thread function placed(i: int, out: words) -> void { if Job.is_worker() { out[i] = 1 } else { out[i] = 0 } } - entry { + entry (threads_st: mut ThreadsState) { if Sync.cpu_count() >= 1 { print(1) } if not Job.is_worker() { print(2) } @@ -34,12 +36,12 @@ program Threads { for i in 0 .. N { if sq[i] != i * i { right = false } } if right { print(3) } - calls = Sync.atomic() - lock = Sync.mutex() + threads_st.calls = Sync.atomic() + threads_st.lock = Sync.mutex() Job.parallel_for(N, fn tally, null) - if Sync.get(calls) == N { print(4) } + if Sync.get(threads_st.calls) == N { print(4) } let want: long = N * (N - 1) / 2 - if total == want { print(5) } + if threads_st.total == want { print(5) } let ran = words(N) Job.parallel_for(N, fn placed, ran) diff --git a/examples/library/ui_anim.ludic b/examples/library/ui_anim.ludic index 64207c23..3a8d0bed 100644 --- a/examples/library/ui_anim.ludic +++ b/examples/library/ui_anim.ludic @@ -4,24 +4,26 @@ import "ludic.ui" program UiAnim { numbers float - var dim: bool = false - view Anim { - dim = dim + state UianimState { + dim: bool = false + } + view Anim (uianim_st: UianimState) { + dim = uianim_st.dim } const PAGE: string = "

a

b

c

" - function at() -> string { - ui_show("a", view_anim(), 0.0, 0.0, 200.0, 200.0) - let r: UiNode = ui_nodes("a", view_anim()) + function at(ui_st: mut UiState) -> string { + ui_show(ui_st, "a", view_anim(), 0.0, 0.0, 200.0, 200.0) + let r: UiNode = ui_nodes(ui_st, "a", view_anim()) return `{int(r.children[0].alpha * 100.0)} {int(r.children[1].alpha * 100.0)} {int(r.children[2].alpha * 100.0)}` } - entry { - ui_load_text(PAGE, "a.xml") - var out = at() - for i in 0 .. 29 { at() } - out = out + " | " + at() - dim = true - for i in 0 .. 29 { at() } - out = out + " | " + at() + entry (ui_st: mut UiState, uianim_st: mut UianimState) { + ui_load_text(ui_st, PAGE, "a.xml") + var out = at(ui_st) + for i in 0 .. 29 { at(ui_st) } + out = out + " | " + at(ui_st) + uianim_st.dim = true + for i in 0 .. 29 { at(ui_st) } + out = out + " | " + at(ui_st) print(out) } } diff --git a/examples/library/ui_box.ludic b/examples/library/ui_box.ludic index 08a97263..2f8f29ff 100644 --- a/examples/library/ui_box.ludic +++ b/examples/library/ui_box.ludic @@ -6,14 +6,14 @@ program UiBox { numbers float const PAGE: string = "

title

" function scale() -> float { return 1.5 } - entry { + entry (ui_st: mut UiState) { let b = new UiBackend b.scale = fn scale - ui_backend(b) - ui_load_text(PAGE, "b.xml") - ui_show("b", null, 20.0, 30.0, 800.0, 600.0) - let p = ui_box("panel") - let none = ui_box("nothing") - print(`scale {ui_scale()} | panel {int(p.x)},{int(p.y)} {int(p.w)}x{int(p.h)} | missing {none == null}`) + ui_backend(ui_st, b) + ui_load_text(ui_st, PAGE, "b.xml") + ui_show(ui_st, "b", null, 20.0, 30.0, 800.0, 600.0) + let p = ui_box(ui_st, "panel") + let none = ui_box(ui_st, "nothing") + print(`scale {ui_scale(ui_st)} | panel {int(p.x)},{int(p.y)} {int(p.w)}x{int(p.h)} | missing {none == null}`) } } diff --git a/examples/library/ui_boxes.ludic b/examples/library/ui_boxes.ludic index 62d9f399..7242d520 100644 --- a/examples/library/ui_boxes.ludic +++ b/examples/library/ui_boxes.ludic @@ -6,11 +6,11 @@ import "ludic.ui" import "ui_boxes_parts/Boxes.ludic" program UiBoxes { numbers float - entry { - ui_backend(new UiBackend) - ui_show("Boxes", null, 0.0, 0.0, 400.0, 600.0) - let root: UiNode = ui_nodes("Boxes", null) - ui_place(root, 0.0, 0.0, 400.0, 600.0) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + ui_show(ui_st, "Boxes", null, 0.0, 0.0, 400.0, 600.0) + let root: UiNode = ui_nodes(ui_st, "Boxes", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) let page = root.children[0] let list = page.children[0].children[1] let line = page.children[1] diff --git a/examples/library/ui_calc.ludic b/examples/library/ui_calc.ludic index 545ca437..7a4eea33 100644 --- a/examples/library/ui_calc.ludic +++ b/examples/library/ui_calc.ludic @@ -5,12 +5,12 @@ program UiCalc { numbers float const STYLE: string = "" const BODY: string = "
" - entry { - ui_backend(new UiBackend) - ui_load_text("" + STYLE + BODY + "", "c.xml") - ui_show("c", null, 0.0, 0.0, 400.0, 600.0) - let r: UiNode = ui_nodes("c", null) - ui_place(r, 0.0, 0.0, 400.0, 600.0) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + ui_load_text(ui_st, "" + STYLE + BODY + "", "c.xml") + ui_show(ui_st, "c", null, 0.0, 0.0, 400.0, 600.0) + let r: UiNode = ui_nodes(ui_st, "c", null) + ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0) let box = r.children[4] let p = box.children[0] let q = box.children[1] diff --git a/examples/library/ui_component.ludic b/examples/library/ui_component.ludic index b79a3074..cac2a70b 100644 --- a/examples/library/ui_component.ludic +++ b/examples/library/ui_component.ludic @@ -11,18 +11,18 @@ program UiComponent { if n.text != "" { push(out, n.text) } for i in 0 .. len(n.children) { texts(n.children[i], out) } } - function frame() -> UiNode { - let root: UiNode = ui_nodes("App", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + function frame(ui_st: mut UiState) -> UiNode { + let root: UiNode = ui_nodes(ui_st, "App", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root } - entry { - var root = frame() + entry (ui_st: mut UiState) { + var root = frame(ui_st) let b = root.children[0].children[2] - ui_press(b.children[1]) - ui_press(b.children[1]) - ui_press(root.children[0].children[1].children[1]) - root = frame() + ui_press(ui_st, b.children[1]) + ui_press(ui_st, b.children[1]) + ui_press(ui_st, root.children[0].children[1].children[1]) + root = frame(ui_st) let out = new []string texts(root, out) var joined = "" @@ -30,8 +30,8 @@ program UiComponent { print(joined) let b2 = root.children[0].children[2] print(`b margin {int(b2.mt)}; b text {b2.children[0].fg}; outside {root.children[0].children[3].fg}; button {int(b2.children[1].ch)}`) - ui_press(b2.children[2]) - root = frame() + ui_press(ui_st, b2.children[2]) + root = frame(ui_st) print(root.children[0].children[2].children[0].text) } } diff --git a/examples/library/ui_controls.ludic b/examples/library/ui_controls.ludic index 1cde14b9..7e4508a7 100644 --- a/examples/library/ui_controls.ludic +++ b/examples/library/ui_controls.ludic @@ -6,11 +6,11 @@ import "ludic.ui" import "ui_controls_parts/Settings.ludic" program UiControls { numbers float - function frame(i: UiInput) -> UiNode { - ui_input(i) - ui_show("Settings", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("Settings", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + function frame(ui_st: mut UiState, i: UiInput) -> UiNode { + ui_input(ui_st, i) + ui_show(ui_st, "Settings", null, 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "Settings", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root } function press(tab: bool, down: bool, right: bool, enter: bool) -> UiInput { @@ -26,30 +26,30 @@ program UiControls { let last = page.children[len(page.children) - 1] return last.text } - entry { - ui_backend(new UiBackend) - var root = frame(new UiInput) - root = frame(press(true, false, false, false)) - root = frame(press(false, false, false, true)) - root = frame(press(false, true, false, false)) - root = frame(press(false, false, false, true)) - root = frame(press(false, true, false, false)) - root = frame(press(false, false, true, false)) - root = frame(press(false, false, true, false)) - root = frame(press(false, true, false, false)) - root = frame(press(false, false, true, false)) - root = frame(press(false, true, false, false)) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + var root = frame(ui_st, new UiInput) + root = frame(ui_st, press(true, false, false, false)) + root = frame(ui_st, press(false, false, false, true)) + root = frame(ui_st, press(false, true, false, false)) + root = frame(ui_st, press(false, false, false, true)) + root = frame(ui_st, press(false, true, false, false)) + root = frame(ui_st, press(false, false, true, false)) + root = frame(ui_st, press(false, false, true, false)) + root = frame(ui_st, press(false, true, false, false)) + root = frame(ui_st, press(false, false, true, false)) + root = frame(ui_st, press(false, true, false, false)) let typed = new UiInput typed.typed = "lovelace" - root = frame(typed) + root = frame(ui_st, typed) let back = new UiInput back.backspace = true - root = frame(back) - root = frame(press(false, true, false, false)) - root = frame(press(false, false, false, true)) + root = frame(ui_st, back) + root = frame(ui_st, press(false, true, false, false)) + root = frame(ui_st, press(false, false, false, true)) let k = new UiInput k.key = 70 - root = frame(k) + root = frame(ui_st, k) print(state(root)) let range = root.children[0].children[2] let track = range.children[1] @@ -57,15 +57,15 @@ program UiControls { at.x = track.x + track.cw * 0.8 at.y = track.y + 2.0 at.down = true - root = frame(at) - root = frame(new UiInput) + root = frame(ui_st, at) + root = frame(ui_st, new UiInput) let list = root.children[0].children[6] let w = new UiInput w.x = list.x + 5.0 w.y = list.y + 5.0 w.wheel = -1.0 - root = frame(w) - root = frame(new UiInput) + root = frame(ui_st, w) + root = frame(ui_st, new UiInput) print(`{state(root)}; scrolled {int(root.children[0].children[6].scroll_y)}`) } } diff --git a/examples/library/ui_css.ludic b/examples/library/ui_css.ludic index b1500bdb..90a3feac 100644 --- a/examples/library/ui_css.ludic +++ b/examples/library/ui_css.ludic @@ -9,12 +9,12 @@ program UiCss { } const LOOK: string = "screen { --gap: 6px; --accent: #ff8800 }\n.panel { position: relative; width: 300px; height: 200px; padding: 10px }\n.badge { position: absolute; right: 5px; bottom: 5px; width: 40px; height: 20px; background: var(--accent) }\n.corner { position: fixed; left: 0; top: 0; width: 10vw; height: 2rem }\n.nudge { position: relative; left: 7px; top: 3px }\n.big { font-size: 2em }\n.missing { color: var(--nope, #00ff00) }\n@media (max-width: 500px) { .wide-only { display: none } }\n@media (min-width: 500px) { .narrow-only { display: none } }\nli + li { margin-top: var(--gap) }\nh3 ~ p { color: var(--accent) }\nli:nth-child(2n+1) { color: #0000ff }\ninput:checked { opacity: 0.25 }\n.list { overflow: auto; height: 50px }\n.para { width: 120px }\n" const PAGE: string = "\n\n\n
!

moved

big

\n
\n

wide

narrow

\n
  • a
  • b
  • c
\n

h

after

fallback

\n \n

1

2

3

4

\n

a sentence long enough that it has to wrap onto several lines

\n\n\n" - entry { - ui_load_text(LOOK, "ui/look.lss") - ui_load_text(PAGE, "ui/css.xml") - ui_viewport(400.0, 300.0) - let root: UiNode = ui_nodes("css", view_css()) - ui_place(root, 0.0, 0.0, 400.0, 300.0) + entry (ui_st: mut UiState) { + ui_load_text(ui_st, LOOK, "ui/look.lss") + ui_load_text(ui_st, PAGE, "ui/css.xml") + ui_viewport(ui_st, 400.0, 300.0) + let root: UiNode = ui_nodes(ui_st, "css", view_css()) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0) let panel = root.children[0] let badge = panel.children[0] let ul = root.children[3] diff --git a/examples/library/ui_ease.ludic b/examples/library/ui_ease.ludic index 1504f61b..027352bb 100644 --- a/examples/library/ui_ease.ludic +++ b/examples/library/ui_ease.ludic @@ -4,30 +4,32 @@ import "ludic.ui" program UiEase { numbers float - var dim: bool = false - var now: float = 0.0 - view Ease { - dim = dim + state UieaseState { + dim: bool = false + now: float = 0.0 + } + view Ease (uiease_st: UieaseState) { + dim = uiease_st.dim } const STYLE: string = "" const BODY: string = "

a

b

c

" - function clock() -> float { return now } - function nodes(name: string) -> UiNode { - ui_show(name, view_ease(), 0.0, 0.0, 200.0, 200.0) - return ui_nodes(name, view_ease()) + function clock(uiease_st: UieaseState) -> float { return uiease_st.now } + function nodes(ui_st: mut UiState, name: string) -> UiNode { + ui_show(ui_st, name, view_ease(), 0.0, 0.0, 200.0, 200.0) + return ui_nodes(ui_st, name, view_ease()) } # one rate, on its own screen: the slide from its first frame, the fade from half a second in - function at_rate(hz: int) -> string { + function at_rate(ui_st: mut UiState, uiease_st: mut UieaseState, hz: int) -> string { let name = `r{hz}` - dim = false + uiease_st.dim = false var mid = "" var slid = false var landed = false var over = false for k in 0 .. hz + 1 { - now = float(k) / float(hz) - if k == hz / 2 { dim = true } - let r = nodes(name) + uiease_st.now = float(k) / float(hz) + if k == hz / 2 { uiease_st.dim = true } + let r = nodes(ui_st, name) let go = r.children[0] let soft = r.children[1] let want = r.children[2].alpha @@ -39,17 +41,17 @@ program UiEase { } return `{hz}: {mid} slid {slid} landed {landed} left {over}` } - entry { - ui_backend(new UiBackend) - ui_clock(fn clock) + entry (ui_st: mut UiState, uiease_st: mut UieaseState) { + ui_backend(ui_st, new UiBackend) + ui_clock(ui_st, fn clock) var page = "" + STYLE for i in 0 .. 4 { let hz = 60 * (i + 1) page = page + `` + BODY } - ui_load_text(page + "", "e.xml") + ui_load_text(ui_st, page + "", "e.xml") var out = "" - for i in 0 .. 4 { out = out + at_rate(60 * (i + 1)) + "; " } + for i in 0 .. 4 { out = out + at_rate(ui_st, uiease_st, 60 * (i + 1)) + "; " } print(out) } } diff --git a/examples/library/ui_fill.ludic b/examples/library/ui_fill.ludic index dff937b3..dba16273 100644 --- a/examples/library/ui_fill.ludic +++ b/examples/library/ui_fill.ludic @@ -4,21 +4,23 @@ import "ludic.ui" program UiFill { numbers float - var rects: []string = new []string - var pics: []string = new []string - var clips: int = 0 + state UifillState { + rects: []string = new []string + pics: []string = new []string + clips: int = 0 + } const STYLE: string = "" const BODY: string = "
" - function rec_rect(x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) } - function rec_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) } - function rec_clip(x: float, y: float, w: float, h: float) -> void { clips += 1 } + function rec_rect(uifill_st: mut UifillState, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(uifill_st.rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) } + function rec_image(uifill_st: mut UifillState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(uifill_st.pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) } + function rec_clip(uifill_st: mut UifillState, x: float, y: float, w: float, h: float) -> void { uifill_st.clips += 1 } function no_clip() -> void { } function no_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { } function nat_w(src: string) -> float { return 200.0 } function nat_h(src: string) -> float { return 100.0 } function knob_measure(n: UiNode, avail: float) -> void { } function knob_draw(n: UiNode) -> void { } - entry { + entry (ui_st: mut UiState, uifill_st: UifillState) { let b = new UiBackend b.rect = fn rec_rect b.text = fn no_text @@ -27,13 +29,13 @@ program UiFill { b.image_h = fn nat_h b.clip = fn rec_clip b.unclip = fn no_clip - ui_backend(b) - ui_native("knob", fn knob_measure, fn knob_draw) - ui_load_text("" + STYLE + BODY + "", "f.xml") - ui_show("f", null, 0.0, 0.0, 400.0, 600.0) - let r: UiNode = ui_nodes("f", null) - ui_place(r, 0.0, 0.0, 400.0, 600.0) - let n = len(rects) - print(`{rects[0]} .. {rects[49]} | {rects[50]} .. {rects[59]} .. {rects[69]} of {n} | {pics[0]} | {pics[1]} clipped {clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`) + ui_backend(ui_st, b) + ui_native(ui_st, "knob", fn knob_measure, fn knob_draw) + ui_load_text(ui_st, "" + STYLE + BODY + "", "f.xml") + ui_show(ui_st, "f", null, 0.0, 0.0, 400.0, 600.0) + let r: UiNode = ui_nodes(ui_st, "f", null) + ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0) + let n = len(uifill_st.rects) + print(`{uifill_st.rects[0]} .. {uifill_st.rects[49]} | {uifill_st.rects[50]} .. {uifill_st.rects[59]} .. {uifill_st.rects[69]} of {n} | {uifill_st.pics[0]} | {uifill_st.pics[1]} clipped {uifill_st.clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`) } } diff --git a/examples/library/ui_flex.ludic b/examples/library/ui_flex.ludic index 21cde37a..477a4091 100644 --- a/examples/library/ui_flex.ludic +++ b/examples/library/ui_flex.ludic @@ -4,19 +4,21 @@ import "ludic.ui" program UiFlex { numbers float - var picked: int = 1 - view Tags { - picked = picked + state UiflexState { + picked: int = 1 + } + view Tags (uiflex_st: UiflexState) { + picked = uiflex_st.picked } const BASE: string = "/* base.lss - every text small */\ntext { size: 10 }\n" const THEME: string = "/* theme.lss - the base, and a colour for what is on */\n@import \"base.lss\";\n.on { color: #ff0000 }\n" const PAGE: string = "\n\n\n\n \n ab\n \n lmr\n \n t{i}\n \n x\n\n\n" - entry { - ui_load_text(BASE, "ui/base.lss") - ui_load_text(THEME, "ui/theme.lss") - ui_load_text(PAGE, "ui/flex.xml") - let root: UiNode = ui_nodes("flex", view_tags()) - ui_place(root, 0.0, 0.0, 400.0, 300.0) + entry (ui_st: mut UiState) { + ui_load_text(ui_st, BASE, "ui/base.lss") + ui_load_text(ui_st, THEME, "ui/theme.lss") + ui_load_text(ui_st, PAGE, "ui/flex.xml") + let root: UiNode = ui_nodes(ui_st, "flex", view_tags()) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0) let bar = root.children[0] let wrap = root.children[2] let tall = root.children[3] diff --git a/examples/library/ui_focus_tip.ludic b/examples/library/ui_focus_tip.ludic index 356de6a9..b27495ca 100644 --- a/examples/library/ui_focus_tip.ludic +++ b/examples/library/ui_focus_tip.ludic @@ -4,36 +4,38 @@ import "ludic.ui" program UiFocusTip { numbers float - var said: []string = new []string - var now: float = 0.0 - const PAGE: string = "" - function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `{s} {int(x)},{int(y)}`) } - function clock() -> float { return now } - function frame(i: UiInput, secs: float) -> string { - now = now + secs - said = new []string - ui_input(i) - ui_show("f", null, 0.0, 0.0, 400.0, 400.0) - return said[len(said) - 1] + state UifocustipState { + said: []string = new []string + now: float = 0.0 } - entry { + const PAGE: string = "" + function rec_text(uifocustip_st: mut UifocustipState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uifocustip_st.said, `{s} {int(x)},{int(y)}`) } + function clock(uifocustip_st: UifocustipState) -> float { return uifocustip_st.now } + function frame(ui_st: mut UiState, uifocustip_st: mut UifocustipState, i: UiInput, secs: float) -> string { + uifocustip_st.now = uifocustip_st.now + secs + uifocustip_st.said = new []string + ui_input(ui_st, i) + ui_show(ui_st, "f", null, 0.0, 0.0, 400.0, 400.0) + return uifocustip_st.said[len(uifocustip_st.said) - 1] + } + entry (ui_st: mut UiState, uifocustip_st: mut UifocustipState) { let b = new UiBackend b.text = fn rec_text - ui_backend(b) - ui_clock(fn clock) - ui_load_text(PAGE, "f.xml") - frame(new UiInput, 0.0) + ui_backend(ui_st, b) + ui_clock(ui_st, fn clock) + ui_load_text(ui_st, PAGE, "f.xml") + frame(ui_st, uifocustip_st, new UiInput, 0.0) let tab = new UiInput tab.tab = true - frame(tab, 0.0) - let early = frame(new UiInput, 0.2) - let shown = frame(new UiInput, 0.4) - frame(tab, 0.0) - let next = frame(new UiInput, 0.6) + frame(ui_st, uifocustip_st, tab, 0.0) + let early = frame(ui_st, uifocustip_st, new UiInput, 0.2) + let shown = frame(ui_st, uifocustip_st, new UiInput, 0.4) + frame(ui_st, uifocustip_st, tab, 0.0) + let next = frame(ui_st, uifocustip_st, new UiInput, 0.6) let moved = new UiInput moved.x = 390.0 moved.y = 390.0 - let gone = frame(moved, 0.6) + let gone = frame(ui_st, uifocustip_st, moved, 0.6) print(`{early} | {shown} | {next} | {gone}`) } } diff --git a/examples/library/ui_gamepad.ludic b/examples/library/ui_gamepad.ludic index dc4a27bb..26d0fe14 100644 --- a/examples/library/ui_gamepad.ludic +++ b/examples/library/ui_gamepad.ludic @@ -5,19 +5,21 @@ import "ludic.ui" import "ui_gamepad_parts/Pad.ludic" program UiGamepad { numbers float - var now: float = 0.0 - function clock() -> float { return now } - function frame(i: UiInput) -> string { - ui_input(i) - ui_show("Pad", null, 0.0, 0.0, 400.0, 600.0) - let page = ui_nodes("Pad", null).children[0] + state UigamepadState { + now: float = 0.0 + } + function clock(uigamepad_st: UigamepadState) -> float { return uigamepad_st.now } + function frame(ui_st: mut UiState, i: UiInput) -> string { + ui_input(ui_st, i) + ui_show(ui_st, "Pad", null, 0.0, 0.0, 400.0, 600.0) + let page = ui_nodes(ui_st, "Pad", null).children[0] return page.children[len(page.children) - 1].text } # `what` held for `secs` at `hz`, then let go - function hold(what: string, secs: float, hz: int) -> string { + function hold(ui_st: mut UiState, uigamepad_st: mut UigamepadState, what: string, secs: float, hz: int) -> string { let frames = int(secs * float(hz) + 0.5) for k in 0 .. frames + 1 { - now = now + 1.0 / float(hz) + uigamepad_st.now = uigamepad_st.now + 1.0 / float(hz) let i = new UiInput i.held_up = what == "up" i.held_down = what == "down" @@ -25,28 +27,28 @@ program UiGamepad { i.held_right = what == "right" i.pad_a = what == "a" i.pad_b = what == "b" - frame(i) + frame(ui_st, i) } - now = now + 1.0 / float(hz) - return frame(new UiInput) + uigamepad_st.now = uigamepad_st.now + 1.0 / float(hz) + return frame(ui_st, new UiInput) } - function tap(what: string) -> string { return hold(what, 0.0, 60) } - entry { - ui_backend(new UiBackend) - ui_clock(fn clock) - frame(new UiInput) - hold("down", 1.0, 240) - let fast = tap("a") - hold("down", 1.0, 60) - hold("up", 0.5, 60) - let slow = tap("a") - for k in 0 .. 8 { tap("down") } - let vol = hold("right", 0.5, 60) - tap("down") - let q = tap("left") - tap("down") - let open = tap("a") - let shut = tap("b") + function tap(ui_st: mut UiState, uigamepad_st: mut UigamepadState, what: string) -> string { return hold(ui_st, uigamepad_st, what, 0.0, 60) } + entry (ui_st: mut UiState, uigamepad_st: mut UigamepadState) { + ui_backend(ui_st, new UiBackend) + ui_clock(ui_st, fn clock) + frame(ui_st, new UiInput) + hold(ui_st, uigamepad_st, "down", 1.0, 240) + let fast = tap(ui_st, uigamepad_st, "a") + hold(ui_st, uigamepad_st, "down", 1.0, 60) + hold(ui_st, uigamepad_st, "up", 0.5, 60) + let slow = tap(ui_st, uigamepad_st, "a") + for k in 0 .. 8 { tap(ui_st, uigamepad_st, "down") } + let vol = hold(ui_st, uigamepad_st, "right", 0.5, 60) + tap(ui_st, uigamepad_st, "down") + let q = tap(ui_st, uigamepad_st, "left") + tap(ui_st, uigamepad_st, "down") + let open = tap(ui_st, uigamepad_st, "a") + let shut = tap(ui_st, uigamepad_st, "b") print(`{fast} | {slow} | {vol} | {q} | {open} | {shut}`) } } diff --git a/examples/library/ui_hold.ludic b/examples/library/ui_hold.ludic index 2a5abf16..42c19af7 100644 --- a/examples/library/ui_hold.ludic +++ b/examples/library/ui_hold.ludic @@ -4,33 +4,35 @@ import "ludic.ui" program UiHold { numbers float - var now: float = 0.0 - var held: float = 0.0 - var frames: int = 0 - var last_t: float = 0.0 - var ends: string = "" - view Craft { + state UiholdState { + now: float = 0.0 + held: float = 0.0 + frames: int = 0 + last_t: float = 0.0 + ends: string = "" + } + view Craft (uihold_st: mut UiholdState) { on tick(dt: float, t: float) { - held = held + dt - frames = frames + 1 - last_t = t + uihold_st.held = uihold_st.held + dt + uihold_st.frames = uihold_st.frames + 1 + uihold_st.last_t = t } - on down() { ends = ends + "d" } - on up() { ends = ends + "u" } + on down() { uihold_st.ends = uihold_st.ends + "d" } + on up() { uihold_st.ends = uihold_st.ends + "u" } } const PAGE: string = "" - function clock() -> float { return now } - function frame(i: UiInput) -> UiNode { - ui_input(i) - ui_show("h", view_craft(), 0.0, 0.0, 400.0, 400.0) - let r: UiNode = ui_nodes("h", view_craft()) - ui_place(r, 0.0, 0.0, 400.0, 400.0) + function clock(uihold_st: UiholdState) -> float { return uihold_st.now } + function frame(ui_st: mut UiState, i: UiInput) -> UiNode { + ui_input(ui_st, i) + ui_show(ui_st, "h", view_craft(), 0.0, 0.0, 400.0, 400.0) + let r: UiNode = ui_nodes(ui_st, "h", view_craft()) + ui_place(ui_st, r, 0.0, 0.0, 400.0, 400.0) return r.children[0] } # held for `secs` at `hz`, by the pointer (off the button after the first frame) or by A - function hold(b: UiNode, secs: float, hz: int, pad: bool) -> string { - held = 0.0 - frames = 0 + function hold(ui_st: mut UiState, uihold_st: mut UiholdState, b: UiNode, secs: float, hz: int, pad: bool) -> string { + uihold_st.held = 0.0 + uihold_st.frames = 0 let n = int(secs * float(hz) + 0.5) for k in 0 .. n + 1 { let i = new UiInput @@ -39,21 +41,21 @@ program UiHold { i.x = b.x + 5.0 i.y = b.y + 5.0 if k > 0 and not pad { i.x = 390.0 } - frame(i) - now = now + 1.0 / float(hz) + frame(ui_st, i) + uihold_st.now = uihold_st.now + 1.0 / float(hz) } - frame(new UiInput) - now = now + 1.0 / float(hz) - return `{frames} frames {int(Math.round(held * 1000.0))} ms t {int(Math.round(last_t * 1000.0))}` + frame(ui_st, new UiInput) + uihold_st.now = uihold_st.now + 1.0 / float(hz) + return `{uihold_st.frames} frames {int(Math.round(uihold_st.held * 1000.0))} ms t {int(Math.round(uihold_st.last_t * 1000.0))}` } - entry { - ui_backend(new UiBackend) - ui_clock(fn clock) - ui_load_text(PAGE, "h.xml") - let b = frame(new UiInput) - let slow = hold(b, 0.5, 60, false) - let fast = hold(b, 0.5, 240, false) - let pad = hold(b, 0.25, 60, true) - print(`{slow} | {fast} | pad {pad} | {ends}`) + entry (ui_st: mut UiState, uihold_st: mut UiholdState) { + ui_backend(ui_st, new UiBackend) + ui_clock(ui_st, fn clock) + ui_load_text(ui_st, PAGE, "h.xml") + let b = frame(ui_st, new UiInput) + let slow = hold(ui_st, uihold_st, b, 0.5, 60, false) + let fast = hold(ui_st, uihold_st, b, 0.5, 240, false) + let pad = hold(ui_st, uihold_st, b, 0.25, 60, true) + print(`{slow} | {fast} | pad {pad} | {uihold_st.ends}`) } } diff --git a/examples/library/ui_html.ludic b/examples/library/ui_html.ludic index b4ceafcf..6b22607d 100644 --- a/examples/library/ui_html.ludic +++ b/examples/library/ui_html.ludic @@ -4,25 +4,27 @@ import "ludic.ui" program UiHtml { numbers float - var clicks: int = 0 - view Page { - clicks = clicks - on clicked() { clicks += 1 } + state UihtmlState { + clicks: int = 0 + } + view Page (uihtml_st: mut UihtmlState) { + clicks = uihtml_st.clicks + on clicked() { uihtml_st.clicks += 1 } } const LOOK: string = "/* look.lss */\n.card { padding: 4 8 12 16; margin: 3; border: 2px solid #0000ff; width: 50% }\n#title { font-size: 40 }\nh1.big { font-size: 20 }\n.card p { color: #00ff00 }\nul > li { height: 30 }\nli:first-child { color: #ff0000 }\nli:nth-child(even) { color: #0000ff }\nli:last-child:not(.keep) { display: none }\n[kind=warn] { background-color: #ffff00 }\nbutton:disabled { opacity: 0.5 }\n" const PAGE: string = "\n\n\n

Title

\n

inside

careful
\n

outside

\n
  • one
  • two
  • three
  • gone
\n
\n

mid

\n \n \n
\n
\n" - entry { - ui_load_text(LOOK, "ui/look.lss") - ui_load_text(PAGE, "ui/page.ludic.xml") - var root: UiNode = ui_nodes("page", view_page()) - ui_place(root, 0.0, 0.0, 400.0, 600.0) + entry (ui_st: mut UiState, uihtml_st: UihtmlState) { + ui_load_text(ui_st, LOOK, "ui/look.lss") + ui_load_text(ui_st, PAGE, "ui/page.ludic.xml") + var root: UiNode = ui_nodes(ui_st, "page", view_page()) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) let card = root.children[1] let items = root.children[3].children print(`id beats class: {int(root.children[0].rsize)}; card {int(card.cw)} wide at {int(card.x)},{int(card.y)} pad {int(card.pt)} {int(card.pr)} {int(card.pb)} {int(card.pl)} border {int(card.border)}`) print(`inside {card.children[0].fg}, outside {root.children[2].fg}; warn bg {card.children[1].bg}; items {len(items)} of 30: {int(items[0].ch)}; colours {items[0].fg} {items[1].fg} {items[2].fg}; hr {int(root.children[4].ch)}; children {len(root.children)}`) - ui_press(root.children[6]) - root = ui_nodes("page", view_page()) - ui_place(root, 0.0, 0.0, 400.0, 600.0) - print(`clicks {clicks}; the button now disabled {not root.children[6].enabled} at opacity {root.children[6].alpha}; text-align {root.children[5].talign}`) + ui_press(ui_st, root.children[6]) + root = ui_nodes(ui_st, "page", view_page()) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) + print(`clicks {uihtml_st.clicks}; the button now disabled {not root.children[6].enabled} at opacity {root.children[6].alpha}; text-align {root.children[5].talign}`) } } diff --git a/examples/library/ui_inputs.ludic b/examples/library/ui_inputs.ludic index 3ec19ee8..4a995afc 100644 --- a/examples/library/ui_inputs.ludic +++ b/examples/library/ui_inputs.ludic @@ -8,11 +8,11 @@ import "ui_inputs_parts/Form.ludic" import "ui_inputs_parts/Tag.ludic" program UiInputs { numbers float - function frame(i: UiInput) -> UiNode { - ui_input(i) - ui_show("Form", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("Form", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + function frame(ui_st: mut UiState, i: UiInput) -> UiNode { + ui_input(ui_st, i) + ui_show(ui_st, "Form", null, 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "Form", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root.children[0] } function keys(tab: bool, shift: bool, enter: bool, left: bool) -> UiInput { @@ -32,52 +32,52 @@ program UiInputs { } function shown_state(page: UiNode) -> string { return page.children[len(page.children) - 1].text } function last(n: UiNode) -> string { return n.children[len(n.children) - 1].text } - entry { - ui_backend(new UiBackend) - var page = frame(new UiInput) - frame(keys(true, false, false, false)) - frame(typed("12")) - page = frame(keys(false, false, true, false)) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + var page = frame(ui_st, new UiInput) + frame(ui_st, keys(true, false, false, false)) + frame(ui_st, typed("12")) + page = frame(ui_st, keys(false, false, true, false)) let shown = page.children[0].children[1].children[0].text let a = shown_state(page) - frame(typed("99")) - page = frame(keys(true, false, false, false)) + frame(ui_st, typed("99")) + page = frame(ui_st, keys(true, false, false, false)) let b = shown_state(page) - frame(keys(true, true, false, false)) - page = frame(keys(false, false, false, true)) + frame(ui_st, keys(true, true, false, false)) + page = frame(ui_st, keys(false, false, false, true)) let c = shown_state(page) let note = page.children[0].children[0].children[1].text - frame(keys(true, false, false, false)) + frame(ui_st, keys(true, false, false, false)) let space = page.children[1].children[1].children[0].text - frame(keys(false, false, true, false)) - let cap = ui_capturing() - page = frame(keys(true, false, false, false)) + frame(ui_st, keys(false, false, true, false)) + let cap = ui_capturing(ui_st) + page = frame(ui_st, keys(true, false, false, false)) let d = shown_state(page) - frame(keys(false, false, true, false)) + frame(ui_st, keys(false, false, true, false)) let esc = new UiInput esc.escape = true esc.key = Key.Escape - page = frame(esc) + page = frame(ui_st, esc) let e = shown_state(page) let back = new UiInput back.backspace = true - page = frame(back) + page = frame(ui_st, back) let f = shown_state(page) - frame(keys(true, false, false, false)) + frame(ui_st, keys(true, false, false, false)) let right = new UiInput right.right = true - page = frame(right) - page = frame(new UiInput) + page = frame(ui_st, right) + page = frame(ui_st, new UiInput) let button = page.children[4] let click = new UiInput click.x = button.x + 2.0 click.y = button.y + 2.0 click.down = true - frame(click) + frame(ui_st, click) let up = new UiInput up.x = click.x up.y = click.y - page = frame(up) + page = frame(ui_st, up) print(`count {shown} {a} | {b} | {c} | {note} | {space} listening {cap} {d} | {e} | {f} | {last(page.children[2])} {last(page.children[3])} | {shown_state(page)} | {page.children[5].text}`) } } diff --git a/examples/library/ui_keycap.ludic b/examples/library/ui_keycap.ludic index 39e9898f..6c9fbc3d 100644 --- a/examples/library/ui_keycap.ludic +++ b/examples/library/ui_keycap.ludic @@ -4,52 +4,54 @@ import "ludic.ui" program UiKeycap { numbers float - var bind: int = 32 - var pressed: int = 0 - view Keys { - bind = bind - pressed = pressed - on set_bind(v: int) { bind = v } - on press() { pressed = pressed + 1 } + state UikeycapState { + bind: int = 32 + pressed: int = 0 + } + view Keys (uikeycap_st: mut UikeycapState) { + bind = uikeycap_st.bind + pressed = uikeycap_st.pressed + on set_bind(v: int) { uikeycap_st.bind = v } + on press() { uikeycap_st.pressed = uikeycap_st.pressed + 1 } } const PAGE: string = "" - function frame(x: float, y: float, b: int) -> UiNode { + function frame(ui_st: mut UiState, x: float, y: float, b: int) -> UiNode { let i = new UiInput i.x = x i.y = y i.down = b == 0 i.down_right = b == 1 i.down_middle = b == 2 - ui_input(i) - ui_show("k", view_keys(), 0.0, 0.0, 400.0, 400.0) - let r: UiNode = ui_nodes("k", view_keys()) - ui_place(r, 0.0, 0.0, 400.0, 400.0) + ui_input(ui_st, i) + ui_show(ui_st, "k", view_keys(), 0.0, 0.0, 400.0, 400.0) + let r: UiNode = ui_nodes(ui_st, "k", view_keys()) + ui_place(ui_st, r, 0.0, 0.0, 400.0, 400.0) return r } - function listen(k: UiNode) -> int { - frame(k.x + 5.0, k.y + 5.0, 0) - frame(k.x + 5.0, k.y + 5.0, -1) - return frame(-1.0, -1.0, -1).children[0].bg + function listen(ui_st: mut UiState, k: UiNode) -> int { + frame(ui_st, k.x + 5.0, k.y + 5.0, 0) + frame(ui_st, k.x + 5.0, k.y + 5.0, -1) + return frame(ui_st, -1.0, -1.0, -1).children[0].bg } - function take(at: UiNode, b: int) -> string { - frame(at.x + 5.0, at.y + 5.0, b) - frame(at.x + 5.0, at.y + 5.0, -1) - let r = frame(-1.0, -1.0, -1) + function take(ui_st: mut UiState, uikeycap_st: UikeycapState, at: UiNode, b: int) -> string { + frame(ui_st, at.x + 5.0, at.y + 5.0, b) + frame(ui_st, at.x + 5.0, at.y + 5.0, -1) + let r = frame(ui_st, -1.0, -1.0, -1) let f = r.children[0] - return `{bind} {f.children[1].children[0].text} {f.bg}` + return `{uikeycap_st.bind} {f.children[1].children[0].text} {f.bg}` } - entry { - ui_backend(new UiBackend) - ui_load_text(PAGE, "k.xml") - let r = frame(-1.0, -1.0, -1) + entry (ui_st: mut UiState, uikeycap_st: UikeycapState) { + ui_backend(ui_st, new UiBackend) + ui_load_text(ui_st, PAGE, "k.xml") + let r = frame(ui_st, -1.0, -1.0, -1) let k = r.children[0] let btn = r.children[1] - let red = listen(k) - let right = take(btn, 1) - listen(k) - let middle = take(btn, 2) - listen(k) - let left = take(btn, 0) - print(`listening {red} | {right} | {middle} | {left} | pressed {pressed}`) + let red = listen(ui_st, k) + let right = take(ui_st, uikeycap_st, btn, 1) + listen(ui_st, k) + let middle = take(ui_st, uikeycap_st, btn, 2) + listen(ui_st, k) + let left = take(ui_st, uikeycap_st, btn, 0) + print(`listening {red} | {right} | {middle} | {left} | pressed {uikeycap_st.pressed}`) } } diff --git a/examples/library/ui_look.ludic b/examples/library/ui_look.ludic index 9d5beab7..fbecd017 100644 --- a/examples/library/ui_look.ludic +++ b/examples/library/ui_look.ludic @@ -10,54 +10,56 @@ import "ui_look_parts/Inner.ludic" import "ui_look_parts/Menu.ludic" program UiLook { numbers float - var said: []string = new []string - var seen: float = 0.0 + state UilookState { + said: []string = new []string + seen: float = 0.0 + } function rec_rect(x: float, y: float, w: float, h: float, c: int, a: float) -> void { } - function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `text {s} {int(x)},{int(y)} {ui_hex(c)}`) } - function rec_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(said, `image {src} {ui_hex(c)}`) } - function rec_nine(src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void { push(said, `nine {int(w)} {ui_hex(c)}`) } + function rec_text(uilook_st: mut UilookState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uilook_st.said, `text {s} {int(x)},{int(y)} {ui_hex(c)}`) } + function rec_image(uilook_st: mut UilookState, src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(uilook_st.said, `image {src} {ui_hex(c)}`) } + function rec_nine(uilook_st: mut UilookState, src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void { push(uilook_st.said, `nine {int(w)} {ui_hex(c)}`) } function gauge_measure(n: UiNode, avail: float) -> void { } - function gauge_draw(n: UiNode) -> void { seen = ui_opacity() } - function count(s: string) -> int { + function gauge_draw(ui_st: UiState, uilook_st: mut UilookState, n: UiNode) -> void { uilook_st.seen = ui_opacity(ui_st) } + function count(uilook_st: UilookState, s: string) -> int { var n = 0 - for i in 0 .. len(said) { - if Text.starts_with(said[i], s) { n += 1 } + for i in 0 .. len(uilook_st.said) { + if Text.starts_with(uilook_st.said[i], s) { n += 1 } } return n } - function has(s: string) -> string { - for i in 0 .. len(said) { - if Text.starts_with(said[i], s) { return said[i] } + function has(uilook_st: UilookState, s: string) -> string { + for i in 0 .. len(uilook_st.said) { + if Text.starts_with(uilook_st.said[i], s) { return uilook_st.said[i] } } return "-" } - function show(name: string, i: UiInput) -> UiNode { - ui_input(i) - said = new []string - ui_show(name, null, 0.0, 0.0, 400.0, 300.0) - let root: UiNode = ui_nodes(name, null) - ui_place(root, 0.0, 0.0, 400.0, 300.0) + function show(ui_st: mut UiState, uilook_st: mut UilookState, name: string, i: UiInput) -> UiNode { + ui_input(ui_st, i) + uilook_st.said = new []string + ui_show(ui_st, name, null, 0.0, 0.0, 400.0, 300.0) + let root: UiNode = ui_nodes(ui_st, name, null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 300.0) return root.children[0] } - entry { + entry (ui_st: mut UiState, uilook_st: mut UilookState) { let b = new UiBackend b.rect = fn rec_rect b.text = fn rec_text b.image = fn rec_image b.nine = fn rec_nine - ui_backend(b) - ui_native("gauge", fn gauge_measure, fn gauge_draw) - var page = show("Look", new UiInput) + ui_backend(ui_st, b) + ui_native(ui_st, "gauge", fn gauge_measure, fn gauge_draw) + var page = show(ui_st, uilook_st, "Look", new UiInput) let inner = page.children[0] let tip = page.children[7] let rest = new UiInput rest.x = tip.x + 2.0 rest.y = tip.y + 2.0 - for f in 0 .. 40 { page = show("Look", rest) } + for f in 0 .. 40 { page = show(ui_st, uilook_st, "Look", rest) } print(`root {inner.tag}.{inner.classes[0]}.{inner.classes[1]}.{inner.classes[2]} pad {int(inner.pt)} margin {int(inner.mt)} color {ui_hex(inner.fg)}`) - print(`{has("text Hi")} | {has("text Hi 0")} | opacity {seen} | {has("nine 40 #ffffff")} {has("nine 40 #ff0000")} of {count("nine")}`) - print(`{has("image photo")} | {has("image icon")} | {has("text first")} | {has("text second")}`) - let menu = show("Menu", new UiInput) + print(`{has(uilook_st, "text Hi")} | {has(uilook_st, "text Hi 0")} | opacity {uilook_st.seen} | {has(uilook_st, "nine 40 #ffffff")} {has(uilook_st, "nine 40 #ff0000")} of {count(uilook_st, "nine")}`) + print(`{has(uilook_st, "image photo")} | {has(uilook_st, "image icon")} | {has(uilook_st, "text first")} | {has(uilook_st, "text second")}`) + let menu = show(ui_st, uilook_st, "Menu", new UiInput) let a = menu.children[1] let c = menu.children[3] print(`by id at {int(a.x)},{int(a.y)} | flipped at {int(c.x)},{int(c.y)} {int(c.cw)}x{int(c.ch)}`) diff --git a/examples/library/ui_pointer.ludic b/examples/library/ui_pointer.ludic index 1b46386d..dab7cc95 100644 --- a/examples/library/ui_pointer.ludic +++ b/examples/library/ui_pointer.ludic @@ -5,56 +5,58 @@ import "ludic.ui" import "ui_pointer_parts/Chart.ludic" program UiPointer { numbers float - var log: string = "" - var dragged: float = 0.0 + state UipointerState { + log: string = "" + dragged: float = 0.0 + } function map_measure(n: UiNode, avail: float) -> void { } function map_draw(n: UiNode) -> void { } - function map_input(n: UiNode, e: UiPointer) -> bool { - if e.kind == "drag" { dragged = dragged + e.dx } else if e.kind != "pointermove" { log = log + `{e.kind} {int(e.x)},{int(e.y)} {e.button} {int(e.wheel)}; ` } + function map_input(uipointer_st: mut UipointerState, n: UiNode, e: UiPointer) -> bool { + if e.kind == "drag" { uipointer_st.dragged = uipointer_st.dragged + e.dx } else if e.kind != "pointermove" { uipointer_st.log = uipointer_st.log + `{e.kind} {int(e.x)},{int(e.y)} {e.button} {int(e.wheel)}; ` } return true } - function frame(x: float, y: float, down: bool, wheel: float, enter: bool) -> UiNode { + function frame(ui_st: mut UiState, x: float, y: float, down: bool, wheel: float, enter: bool) -> UiNode { let i = new UiInput i.x = x i.y = y i.down = down i.wheel = wheel i.held_enter = enter - ui_input(i) - ui_show("Chart", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("Chart", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + ui_input(ui_st, i) + ui_show(ui_st, "Chart", null, 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "Chart", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root.children[0] } function said(page: UiNode) -> string { return page.children[len(page.children) - 1].text } - entry { - ui_backend(new UiBackend) - ui_native("chartmap", fn map_measure, fn map_draw) - ui_native_input("chartmap", fn map_input) - let page = frame(-1.0, -1.0, false, 0.0, false) + entry (ui_st: mut UiState, uipointer_st: UipointerState) { + ui_backend(ui_st, new UiBackend) + ui_native(ui_st, "chartmap", fn map_measure, fn map_draw) + ui_native_input(ui_st, "chartmap", fn map_input) + let page = frame(ui_st, -1.0, -1.0, false, 0.0, false) let box = page.children[0] let pad = page.children[1] let hold = page.children[2] - frame(box.x + 20.0, box.y + 20.0, true, 0.0, false) - frame(box.x + 20.0, box.y + 20.0, false, 0.0, false) - frame(box.x + 100.0, box.y + 80.0, true, 0.0, false) - frame(box.x + 130.0, box.y + 60.0, true, 0.0, false) - frame(box.x + 300.0, box.y + 300.0, true, 0.0, false) - frame(box.x + 300.0, box.y + 300.0, false, 0.0, false) - frame(box.x + 50.0, box.y + 50.0, false, 1.0, false) - frame(pad.x + 7.0, pad.y + 9.0, true, 0.0, false) - frame(pad.x + 17.0, pad.y + 9.0, true, 0.0, false) - frame(pad.x + 17.0, pad.y + 9.0, false, 0.0, false) - frame(pad.x + 17.0, pad.y + 9.0, false, 2.0, false) - frame(hold.x + 5.0, hold.y + 5.0, true, 0.0, false) - let down = frame(hold.x + 5.0, hold.y + 5.0, true, 0.0, false) + frame(ui_st, box.x + 20.0, box.y + 20.0, true, 0.0, false) + frame(ui_st, box.x + 20.0, box.y + 20.0, false, 0.0, false) + frame(ui_st, box.x + 100.0, box.y + 80.0, true, 0.0, false) + frame(ui_st, box.x + 130.0, box.y + 60.0, true, 0.0, false) + frame(ui_st, box.x + 300.0, box.y + 300.0, true, 0.0, false) + frame(ui_st, box.x + 300.0, box.y + 300.0, false, 0.0, false) + frame(ui_st, box.x + 50.0, box.y + 50.0, false, 1.0, false) + frame(ui_st, pad.x + 7.0, pad.y + 9.0, true, 0.0, false) + frame(ui_st, pad.x + 17.0, pad.y + 9.0, true, 0.0, false) + frame(ui_st, pad.x + 17.0, pad.y + 9.0, false, 0.0, false) + frame(ui_st, pad.x + 17.0, pad.y + 9.0, false, 2.0, false) + frame(ui_st, hold.x + 5.0, hold.y + 5.0, true, 0.0, false) + let down = frame(ui_st, hold.x + 5.0, hold.y + 5.0, true, 0.0, false) let active = down.children[2].bg let mid = said(down) - frame(hold.x + 5.0, hold.y + 90.0, false, 0.0, false) - let off = frame(-1.0, -1.0, false, 0.0, false) - frame(-1.0, -1.0, false, 0.0, true) - let keyed = said(frame(-1.0, -1.0, false, 0.0, true)) - let up = frame(-1.0, -1.0, false, 0.0, false) - print(`{log}dragged {int(dragged)} | {mid} active {active} | {said(off)} | {keyed} | {said(up)} {up.children[2].bg}`) + frame(ui_st, hold.x + 5.0, hold.y + 90.0, false, 0.0, false) + let off = frame(ui_st, -1.0, -1.0, false, 0.0, false) + frame(ui_st, -1.0, -1.0, false, 0.0, true) + let keyed = said(frame(ui_st, -1.0, -1.0, false, 0.0, true)) + let up = frame(ui_st, -1.0, -1.0, false, 0.0, false) + print(`{uipointer_st.log}dragged {int(uipointer_st.dragged)} | {mid} active {active} | {said(off)} | {keyed} | {said(up)} {up.children[2].bg}`) } } diff --git a/examples/library/ui_popover.ludic b/examples/library/ui_popover.ludic index ef1af5af..4aafafdd 100644 --- a/examples/library/ui_popover.ludic +++ b/examples/library/ui_popover.ludic @@ -4,55 +4,55 @@ import "ludic.ui" import "ui_popover_parts/Verbs.ludic" program UiPopover { numbers float - function frame(i: UiInput) -> UiNode { - ui_input(i) - ui_show("Verbs", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("Verbs", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + function frame(ui_st: mut UiState, i: UiInput) -> UiNode { + ui_input(ui_st, i) + ui_show(ui_st, "Verbs", null, 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "Verbs", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root } - function click(x: float, y: float) -> void { + function click(ui_st: mut UiState, x: float, y: float) -> void { let d = new UiInput d.x = x d.y = y d.down = true - frame(d) + frame(ui_st, d) let u = new UiInput u.x = x u.y = y - frame(u) + frame(ui_st, u) } function said(root: UiNode) -> string { let page = root.children[0] return page.children[len(page.children) - 1].text } - entry { - ui_backend(new UiBackend) - var root = frame(new UiInput) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + var root = frame(ui_st, new UiInput) let cell = root.children[0].children[0] - click(cell.x + 2.0, cell.y + 2.0) - root = frame(new UiInput) + click(ui_st, cell.x + 2.0, cell.y + 2.0) + root = frame(ui_st, new UiInput) let tab = new UiInput tab.tab = true - frame(tab) - frame(tab) - root = frame(tab) + frame(ui_st, tab) + frame(ui_st, tab) + root = frame(ui_st, tab) let enter = new UiInput enter.enter = true - root = frame(enter) - root = frame(new UiInput) + root = frame(ui_st, enter) + root = frame(ui_st, new UiInput) let first = said(root) - click(cell.x + 2.0, cell.y + 2.0) - root = frame(new UiInput) + click(ui_st, cell.x + 2.0, cell.y + 2.0) + root = frame(ui_st, new UiInput) let opened = said(root) - click(390.0, 390.0) - root = frame(new UiInput) + click(ui_st, 390.0, 390.0) + root = frame(ui_st, new UiInput) let outside = said(root) - click(cell.x + 2.0, cell.y + 2.0) + click(ui_st, cell.x + 2.0, cell.y + 2.0) let esc = new UiInput esc.escape = true - frame(esc) - root = frame(new UiInput) + frame(ui_st, esc) + root = frame(ui_st, new UiInput) let bar = root.children[0].children[1] print(`{first} | {opened} | {outside} | {said(root)} | fill {int(bar.children[0].cw)} of {int(bar.cw)}`) } diff --git a/examples/library/ui_popover_align.ludic b/examples/library/ui_popover_align.ludic index 127dd987..45b8c6c2 100644 --- a/examples/library/ui_popover_align.ludic +++ b/examples/library/ui_popover_align.ludic @@ -6,11 +6,11 @@ import "ui_popover_align_parts/Menus.ludic" program UiPopoverAlign { numbers float function at(n: UiNode, base: UiNode) -> string { return `{int(n.x - base.x)},{int(n.y - base.y)}` } - entry { - ui_backend(new UiBackend) - ui_show("Menus", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("Menus", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + ui_show(ui_st, "Menus", null, 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "Menus", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) let panel = root.children[0].children[0] let list = root.children[0].children[1] let b = list.children[1] diff --git a/examples/library/ui_popover_mouse.ludic b/examples/library/ui_popover_mouse.ludic index a8a4c9f8..921a0864 100644 --- a/examples/library/ui_popover_mouse.ludic +++ b/examples/library/ui_popover_mouse.ludic @@ -5,24 +5,24 @@ import "ludic.ui" import "ui_popover_mouse_parts/Grid.ludic" program UiPopoverMouse { numbers float - function frame(i: UiInput) -> UiNode { - ui_input(i) - ui_show("Grid", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("Grid", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + function frame(ui_st: mut UiState, i: UiInput) -> UiNode { + ui_input(ui_st, i) + ui_show(ui_st, "Grid", null, 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "Grid", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root } - function click(x: float, y: float) -> string { + function click(ui_st: mut UiState, x: float, y: float) -> string { let d = new UiInput d.x = x d.y = y d.down = true - frame(d) + frame(ui_st, d) let u = new UiInput u.x = x u.y = y - frame(u) - let root = frame(new UiInput) + frame(ui_st, u) + let root = frame(ui_st, new UiInput) let page = root.children[0] return page.children[len(page.children) - 1].text } @@ -34,19 +34,19 @@ program UiPopoverMouse { } return null } - entry { - ui_backend(new UiBackend) - frame(new UiInput) - let opened = click(20.0, 20.0) - let root = frame(new UiInput) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + frame(ui_st, new UiInput) + let opened = click(ui_st, 20.0, 20.0) + let root = frame(ui_st, new UiInput) let drop = find(root, "drop") let cell = find(root, "cell") let over = drop.x >= cell.x and drop.y >= cell.y and drop.x + drop.cw <= cell.x + cell.cw - let dropped = click(drop.x + drop.cw / 2.0, drop.y + drop.ch / 2.0) - click(20.0, 20.0) - let on_cell = click(180.0, 90.0) - click(20.0, 20.0) - let on_nothing = click(390.0, 390.0) + let dropped = click(ui_st, drop.x + drop.cw / 2.0, drop.y + drop.ch / 2.0) + click(ui_st, 20.0, 20.0) + let on_cell = click(ui_st, 180.0, 90.0) + click(ui_st, 20.0, 20.0) + let on_nothing = click(ui_st, 390.0, 390.0) print(`over the cell {over} | {opened} | {dropped} | {on_cell} | {on_nothing}`) } } diff --git a/examples/library/ui_react.ludic b/examples/library/ui_react.ludic index 576e71da..f33159d6 100644 --- a/examples/library/ui_react.ludic +++ b/examples/library/ui_react.ludic @@ -4,18 +4,20 @@ import "ludic.ui" program UiReact { numbers float - var names: []string = new []string - var volume: int = 3 - var mounted: int = 0 - var unmounted: int = 0 - var show_extra: bool = true - view Mixer { - names = names - volume = volume - extra = show_extra - on set_volume(v: int) { volume = v } - on mount() { mounted += 1 } - on unmount() { unmounted += 1 } + state UireactState { + names: []string = new []string + volume: int = 3 + mounted: int = 0 + unmounted: int = 0 + show_extra: bool = true + } + view Mixer (uireact_st: mut UireactState) { + names = uireact_st.names + volume = uireact_st.volume + extra = uireact_st.show_extra + on set_volume(v: int) { uireact_st.volume = v } + on mount() { uireact_st.mounted += 1 } + on unmount() { uireact_st.unmounted += 1 } } # a dial the program draws itself: it measures 40 square and, pressed, turns up by one function dial_measure(n: UiNode, avail: float) -> void { @@ -28,32 +30,32 @@ program UiReact { if n.text != "" { push(out, n.text) } for i in 0 .. len(n.children) { texts(n.children[i], out) } } - function frame() -> UiNode { - ui_show("mix", view_mixer(), 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("mix", view_mixer()) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + function frame(ui_st: mut UiState) -> UiNode { + ui_show(ui_st, "mix", view_mixer(), 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "mix", view_mixer()) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root } - entry { - ui_native("dial", fn dial_measure, fn dial_draw) - push(names, "bass") - push(names, "drums") - ui_load_text(PAGE, "ui/mix.xml") - var root = frame() - ui_press(root.children[2]) - names[0] = "drums" - names[1] = "bass" - root = frame() + entry (ui_st: mut UiState, uireact_st: mut UireactState) { + ui_native(ui_st, "dial", fn dial_measure, fn dial_draw) + push(uireact_st.names, "bass") + push(uireact_st.names, "drums") + ui_load_text(ui_st, PAGE, "ui/mix.xml") + var root = frame(ui_st) + ui_press(ui_st, root.children[2]) + uireact_st.names[0] = "drums" + uireact_st.names[1] = "bass" + root = frame(ui_st) let dial = root.children[3] - ui_event(dial, "change", Value.int(volume + 4)) - ui_event(root.children[4], "change", Value.bool(1)) - show_extra = false - root = frame() + ui_event(ui_st, dial, "change", Value.int(uireact_st.volume + 4)) + ui_event(ui_st, root.children[4], "change", Value.bool(1)) + uireact_st.show_extra = false + root = frame(ui_st) let out = new []string texts(root, out) var joined = "" for i in 0 .. len(out) { joined = joined + out[i] + "|" } print(joined) - print(`volume {volume}; dial {int(dial.cw)}x{int(dial.ch)}; mounted {mounted}, unmounted {unmounted}`) + print(`volume {uireact_st.volume}; dial {int(dial.cw)}x{int(dial.ch)}; mounted {uireact_st.mounted}, unmounted {uireact_st.unmounted}`) } } diff --git a/examples/library/ui_reload.ludic b/examples/library/ui_reload.ludic index aad448a9..9084d431 100644 --- a/examples/library/ui_reload.ludic +++ b/examples/library/ui_reload.ludic @@ -9,21 +9,21 @@ program UiReload { const PATH: string = "build/ui_reload_example.xml" const V1: string = "" const V2: string = "

{title} again {n}

" - function shown() -> string { - let root: UiNode = ui_nodes("note", view_note()) + function shown(ui_st: mut UiState) -> string { + let root: UiNode = ui_nodes(ui_st, "note", view_note()) let first = root.children[0] if len(first.children) > 0 { return first.children[0].text } return first.text } - entry { + entry (ui_st: mut UiState) { Fs.write_text(PATH, V1) - ui_load(PATH) - let root: UiNode = ui_nodes("note", view_note()) - ui_press(root.children[0]) - let before = shown() - let same = ui_reload() + ui_load(ui_st, PATH) + let root: UiNode = ui_nodes(ui_st, "note", view_note()) + ui_press(ui_st, root.children[0]) + let before = shown(ui_st) + let same = ui_reload(ui_st) Fs.write_text(PATH, V2) - let changed = ui_reload() - print(`{before} | reloaded {same} then {changed} | {shown()}`) + let changed = ui_reload(ui_st) + print(`{before} | reloaded {same} then {changed} | {shown(ui_st)}`) } } diff --git a/examples/library/ui_scroll_hold.ludic b/examples/library/ui_scroll_hold.ludic index 6808ae19..70e2811a 100644 --- a/examples/library/ui_scroll_hold.ludic +++ b/examples/library/ui_scroll_hold.ludic @@ -4,29 +4,29 @@ import "ludic.ui" import "ui_scroll_hold_parts/Held.ludic" program UiScrollHold { numbers float - function frame(x: float, y: float, wheel: float) -> UiNode { + function frame(ui_st: mut UiState, x: float, y: float, wheel: float) -> UiNode { let i = new UiInput i.x = x i.y = y i.wheel = wheel - ui_input(i) - ui_show("Held", null, 0.0, 0.0, 400.0, 600.0) - let root: UiNode = ui_nodes("Held", null) - ui_place(root, 0.0, 0.0, 400.0, 600.0) + ui_input(ui_st, i) + ui_show(ui_st, "Held", null, 0.0, 0.0, 400.0, 600.0) + let root: UiNode = ui_nodes(ui_st, "Held", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) return root.children[0] } function at(p: UiNode) -> string { return `{int(p.children[0].scroll_y)} {int(p.children[1].scroll_y)} {int(p.children[2].scroll_y)} {p.children[3].text}` } - entry { - ui_backend(new UiBackend) - let p = frame(-1.0, -1.0, 0.0) - let first = at(frame(-1.0, -1.0, 0.0)) - frame(p.children[0].x + 5.0, p.children[0].y + 5.0, 1.0) - let followed = at(frame(-1.0, -1.0, 0.0)) - frame(p.children[1].x + 5.0, p.children[1].y + 5.0, -1.0) - let fixed = at(frame(-1.0, -1.0, 0.0)) - ui_scroll_set("free", 70.0) - let set = at(frame(-1.0, -1.0, 0.0)) - frame(p.children[2].x + 5.0, p.children[2].y + 5.0, 1.0) - print(`{first} | {followed} | {fixed} | {set} | {at(frame(-1.0, -1.0, 0.0))}`) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + let p = frame(ui_st, -1.0, -1.0, 0.0) + let first = at(frame(ui_st, -1.0, -1.0, 0.0)) + frame(ui_st, p.children[0].x + 5.0, p.children[0].y + 5.0, 1.0) + let followed = at(frame(ui_st, -1.0, -1.0, 0.0)) + frame(ui_st, p.children[1].x + 5.0, p.children[1].y + 5.0, -1.0) + let fixed = at(frame(ui_st, -1.0, -1.0, 0.0)) + ui_scroll_set(ui_st, "free", 70.0) + let set = at(frame(ui_st, -1.0, -1.0, 0.0)) + frame(ui_st, p.children[2].x + 5.0, p.children[2].y + 5.0, 1.0) + print(`{first} | {followed} | {fixed} | {set} | {at(frame(ui_st, -1.0, -1.0, 0.0))}`) } } diff --git a/examples/library/ui_scrollbar.ludic b/examples/library/ui_scrollbar.ludic index b411078e..cbf644e8 100644 --- a/examples/library/ui_scrollbar.ludic +++ b/examples/library/ui_scrollbar.ludic @@ -5,48 +5,50 @@ import "ludic.ui" import "ui_scrollbar_parts/Long.ludic" program UiScrollbar { numbers float - var bx: float = 0.0 - var by: float = 0.0 - function frame(i: UiInput) -> UiNode { - ui_input(i) - ui_show("Long", null, 0.0, 0.0, 400.0, 400.0) - let root: UiNode = ui_nodes("Long", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + state UiscrollbarState { + bx: float = 0.0 + by: float = 0.0 + } + function frame(ui_st: mut UiState, i: UiInput) -> UiNode { + ui_input(ui_st, i) + ui_show(ui_st, "Long", null, 0.0, 0.0, 400.0, 400.0) + let root: UiNode = ui_nodes(ui_st, "Long", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) return root.children[0] } # the pointer at (bx + x, by + y), held or not; how far the box is scrolled then - function at(x: float, y: float, down: bool) -> int { + function at(ui_st: mut UiState, uiscrollbar_st: UiscrollbarState, x: float, y: float, down: bool) -> int { let i = new UiInput - i.x = bx + x - i.y = by + y + i.x = uiscrollbar_st.bx + x + i.y = uiscrollbar_st.by + y i.down = down - return int(frame(i).children[0].scroll_y) + return int(frame(ui_st, i).children[0].scroll_y) } - function wheel(x: float, y: float) -> int { + function wheel(ui_st: mut UiState, uiscrollbar_st: UiscrollbarState, x: float, y: float) -> int { let i = new UiInput - i.x = bx + x - i.y = by + y + i.x = uiscrollbar_st.bx + x + i.y = uiscrollbar_st.by + y i.wheel = 1.0 - frame(i) - return int(frame(new UiInput).children[0].scroll_y) + frame(ui_st, i) + return int(frame(ui_st, new UiInput).children[0].scroll_y) } - entry { - ui_backend(new UiBackend) - let list = frame(new UiInput).children[0] - bx = list.x - by = list.y - let grab = at(195.0, 10.0, true) - let moved = at(195.0, 40.0, true) - let far = at(195.0, 300.0, true) - let back = at(195.0, 25.0, true) - let kept = at(195.0, 25.0, false) - at(0.0 - bx - 1.0, 0.0 - by - 1.0, false) - let jump = at(195.0, 80.0, true) - at(195.0, 80.0, false) - let wheeled = wheel(50.0, 50.0) - at(50.0, 20.0, true) - at(50.0, 20.0, false) - let page = frame(new UiInput) + entry (ui_st: mut UiState, uiscrollbar_st: mut UiscrollbarState) { + ui_backend(ui_st, new UiBackend) + let list = frame(ui_st, new UiInput).children[0] + uiscrollbar_st.bx = list.x + uiscrollbar_st.by = list.y + let grab = at(ui_st, uiscrollbar_st, 195.0, 10.0, true) + let moved = at(ui_st, uiscrollbar_st, 195.0, 40.0, true) + let far = at(ui_st, uiscrollbar_st, 195.0, 300.0, true) + let back = at(ui_st, uiscrollbar_st, 195.0, 25.0, true) + let kept = at(ui_st, uiscrollbar_st, 195.0, 25.0, false) + at(ui_st, uiscrollbar_st, 0.0 - uiscrollbar_st.bx - 1.0, 0.0 - uiscrollbar_st.by - 1.0, false) + let jump = at(ui_st, uiscrollbar_st, 195.0, 80.0, true) + at(ui_st, uiscrollbar_st, 195.0, 80.0, false) + let wheeled = wheel(ui_st, uiscrollbar_st, 50.0, 50.0) + at(ui_st, uiscrollbar_st, 50.0, 20.0, true) + at(ui_st, uiscrollbar_st, 50.0, 20.0, false) + let page = frame(ui_st, new UiInput) print(`grab {grab} moved {moved} far {far} back {back} kept {kept} | jump {jump} | wheel {wheeled} | {page.children[1].text}`) } } diff --git a/examples/library/ui_submit.ludic b/examples/library/ui_submit.ludic index 491f7de8..debe24e6 100644 --- a/examples/library/ui_submit.ludic +++ b/examples/library/ui_submit.ludic @@ -3,43 +3,45 @@ import "ludic.ui" program UiSubmit { numbers float - var said: string = "" - var chat: string = "" - var name: string = "" - view Chat { - chat = chat - name = name - on set_chat(v: string) { chat = v } - on set_name(v: string) { name = v } - on send(v: string) { said = said + "[" + v + "]" } + state UisubmitState { + said: string = "" + chat: string = "" + name: string = "" + } + view Chat (uisubmit_st: mut UisubmitState) { + chat = uisubmit_st.chat + name = uisubmit_st.name + on set_chat(v: string) { uisubmit_st.chat = v } + on set_name(v: string) { uisubmit_st.name = v } + on send(v: string) { uisubmit_st.said = uisubmit_st.said + "[" + v + "]" } } const PAGE: string = "" - function frame(i: UiInput) -> void { - ui_input(i) - ui_show("c", view_chat(), 0.0, 0.0, 400.0, 400.0) + function frame(ui_st: mut UiState, i: UiInput) -> void { + ui_input(ui_st, i) + ui_show(ui_st, "c", view_chat(), 0.0, 0.0, 400.0, 400.0) } - function press(tab: bool, enter: bool, pad: bool, typed: string) -> void { + function press(ui_st: mut UiState, tab: bool, enter: bool, pad: bool, typed: string) -> void { let i = new UiInput i.tab = tab i.enter = enter i.pad_a = pad i.typed = typed - frame(i) - frame(new UiInput) + frame(ui_st, i) + frame(ui_st, new UiInput) } - entry { - ui_backend(new UiBackend) - ui_load_text(PAGE, "c.xml") - frame(new UiInput) - press(true, false, false, "") - press(false, false, false, "hi") - press(false, true, false, "") - let after = `{said} chat '{chat}'` - press(false, false, false, "yo") - press(false, false, true, "") - press(true, false, false, "") - press(false, false, false, "Ada") - press(false, true, false, "") - print(`{after} | {said} chat '{chat}' name '{name}'`) + entry (ui_st: mut UiState, uisubmit_st: UisubmitState) { + ui_backend(ui_st, new UiBackend) + ui_load_text(ui_st, PAGE, "c.xml") + frame(ui_st, new UiInput) + press(ui_st, true, false, false, "") + press(ui_st, false, false, false, "hi") + press(ui_st, false, true, false, "") + let after = `{uisubmit_st.said} chat '{uisubmit_st.chat}'` + press(ui_st, false, false, false, "yo") + press(ui_st, false, false, true, "") + press(ui_st, true, false, false, "") + press(ui_st, false, false, false, "Ada") + press(ui_st, false, true, false, "") + print(`{after} | {uisubmit_st.said} chat '{uisubmit_st.chat}' name '{uisubmit_st.name}'`) } } diff --git a/examples/library/ui_templates.ludic b/examples/library/ui_templates.ludic index 1a380ad6..63ee09cd 100644 --- a/examples/library/ui_templates.ludic +++ b/examples/library/ui_templates.ludic @@ -9,40 +9,42 @@ program UiTemplates { name: string = "" cost: int = 0 } - var stock: []Item = new []Item - var purse: int = 100 - var bought: []int = new []int - function add(name: string, cost: int) -> void { + state UitemplatesState { + stock: []Item = new []Item + purse: int = 100 + bought: []int = new []int + } + function add(uitemplates_st: mut UitemplatesState, name: string, cost: int) -> void { let it = new Item it.name = name it.cost = cost - push(stock, it) + push(uitemplates_st.stock, it) } - view Shop { - purse = purse - stock = stock - owned: int = len(bought) - function afford(i: int) -> bool { return purse >= stock[i].cost } + view Shop (uitemplates_st: mut UitemplatesState) { + purse = uitemplates_st.purse + stock = uitemplates_st.stock + owned: int = len(uitemplates_st.bought) + function afford(i: int) -> bool { return uitemplates_st.purse >= uitemplates_st.stock[i].cost } on buy(i: int) { - purse -= stock[i].cost - push(bought, i) + uitemplates_st.purse -= uitemplates_st.stock[i].cost + push(uitemplates_st.bought, i) } } const KIT: string = "\n${cost}\n\n \n \n {item.name} -\n \n \n {picks}\n \n \n\n\n" const PAGE: string = "\n\n\n \n The purse: ${purse}\n \n \n \n Nothing bought yet\n {owned} bought, the last {pick + 1}\n\n\n" const PRIVATE: string = "" - entry { - add("Rope", 30) - add("Lantern", 80) - ui_load_text(KIT, "ui/kit.xml") - ui_load_text(PAGE, "ui/shop.xml") - ui_load_text(PRIVATE, "ui/peek.xml") - var root: UiNode = ui_nodes("shop", view_shop()) - ui_place(root, 0.0, 0.0, 600.0, 400.0) + entry (ui_st: mut UiState, uitemplates_st: mut UitemplatesState) { + add(uitemplates_st, "Rope", 30) + add(uitemplates_st, "Lantern", 80) + ui_load_text(ui_st, KIT, "ui/kit.xml") + ui_load_text(ui_st, PAGE, "ui/shop.xml") + ui_load_text(ui_st, PRIVATE, "ui/peek.xml") + var root: UiNode = ui_nodes(ui_st, "shop", view_shop()) + ui_place(ui_st, root, 0.0, 0.0, 600.0, 400.0) let press = root.children[1].children[4] - ui_press(ui_hit(root, press.x + 1.0, press.y + 1.0)) - root = ui_nodes("shop", view_shop()) - ui_place(root, 0.0, 0.0, 600.0, 400.0) - print(`purse {purse}, bought {len(bought)}; the lantern is enabled: {root.children[2].children[4].enabled}`) + ui_press(ui_st, ui_hit(root, press.x + 1.0, press.y + 1.0)) + root = ui_nodes(ui_st, "shop", view_shop()) + ui_place(ui_st, root, 0.0, 0.0, 600.0, 400.0) + print(`purse {uitemplates_st.purse}, bought {len(uitemplates_st.bought)}; the lantern is enabled: {root.children[2].children[4].enabled}`) } } diff --git a/examples/library/ui_text.ludic b/examples/library/ui_text.ludic index 73b74967..ec96f882 100644 --- a/examples/library/ui_text.ludic +++ b/examples/library/ui_text.ludic @@ -5,19 +5,21 @@ import "ludic.ui" import "ui_text_parts/Fit.ludic" program UiText { numbers float - var said: []string = new []string - function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, `{s}@{int(size * 10.0)} y{int(y)}`) } + state UitextState { + said: []string = new []string + } + function rec_text(uitext_st: mut UitextState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uitext_st.said, `{s}@{int(size * 10.0)} y{int(y)}`) } function w(s: string, size: float) -> float { return float(len(s)) * size * 0.5 } - entry { + entry (ui_st: mut UiState, uitext_st: UitextState) { let b = new UiBackend b.text = fn rec_text b.measure = fn w - ui_backend(b) - ui_show("Fit", null, 0.0, 0.0, 400.0, 600.0) - let root: UiNode = ui_nodes("Fit", null) - ui_place(root, 0.0, 0.0, 400.0, 600.0) + ui_backend(ui_st, b) + ui_show(ui_st, "Fit", null, 0.0, 0.0, 400.0, 600.0) + let root: UiNode = ui_nodes(ui_st, "Fit", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 600.0) let page = root.children[0] let lines = page.children[3] - print(`{said[0]} | {said[1]} | {said[2]} | lines {len(lines.lines)} {int(lines.mh)} {said[3]} {said[4]} | tall {int(page.children[4].mh)} | em {int(page.children[5].pt)} {int(page.children[6].pt)} | tie {ui_hex(page.children[7].fg)}`) + print(`{uitext_st.said[0]} | {uitext_st.said[1]} | {uitext_st.said[2]} | lines {len(lines.lines)} {int(lines.mh)} {uitext_st.said[3]} {uitext_st.said[4]} | tall {int(page.children[4].mh)} | em {int(page.children[5].pt)} {int(page.children[6].pt)} | tie {ui_hex(page.children[7].fg)}`) } } diff --git a/examples/library/ui_theme.ludic b/examples/library/ui_theme.ludic index 11600312..dc86c079 100644 --- a/examples/library/ui_theme.ludic +++ b/examples/library/ui_theme.ludic @@ -5,10 +5,10 @@ import "ui_theme_parts/Bare.ludic" import "ui_theme_parts/Shell.ludic" program UiTheme { numbers float - entry { - ui_backend(new UiBackend) - let root: UiNode = ui_nodes("Shell", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + let root: UiNode = ui_nodes(ui_st, "Shell", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) let bare = root.children[0].children[0] print(`accent {ui_hex(bare.children[0].fg)} gap {int(bare.gap)}`) } diff --git a/examples/library/ui_translate.ludic b/examples/library/ui_translate.ludic index 2370a584..7fac808c 100644 --- a/examples/library/ui_translate.ludic +++ b/examples/library/ui_translate.ludic @@ -4,8 +4,10 @@ import "ludic.ui" program UiTranslate { numbers float - var said: []string = new []string - var now: float = 0.0 + state UitranslateState { + said: []string = new []string + now: float = 0.0 + } const PAGE: string = "

Hello

Hello

Hello

Hello

a

b

c

" function tr(s: string) -> string { if s == "Hello" { return "Merhaba" } @@ -14,31 +16,31 @@ program UiTranslate { if s == "Line two" { return "Satir iki" } return s } - function rec_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(said, s) } - function clock() -> float { return now } - function tip(root: UiNode, k: int) -> string { + function rec_text(uitranslate_st: mut UitranslateState, x: float, y: float, s: string, size: float, c: int, a: float) -> void { push(uitranslate_st.said, s) } + function clock(uitranslate_st: UitranslateState) -> float { return uitranslate_st.now } + function tip(ui_st: mut UiState, uitranslate_st: mut UitranslateState, root: UiNode, k: int) -> string { let n = root.children[k] let i = new UiInput i.x = n.x + 1.0 i.y = n.y + 1.0 - ui_input(i) - ui_show("t", null, 0.0, 0.0, 400.0, 400.0) - now = now + 1.0 - said = new []string - ui_input(i) - ui_show("t", null, 0.0, 0.0, 400.0, 400.0) - return `{said[len(said) - 2]}/{said[len(said) - 1]}` + ui_input(ui_st, i) + ui_show(ui_st, "t", null, 0.0, 0.0, 400.0, 400.0) + uitranslate_st.now = uitranslate_st.now + 1.0 + uitranslate_st.said = new []string + ui_input(ui_st, i) + ui_show(ui_st, "t", null, 0.0, 0.0, 400.0, 400.0) + return `{uitranslate_st.said[len(uitranslate_st.said) - 2]}/{uitranslate_st.said[len(uitranslate_st.said) - 1]}` } - entry { + entry (ui_st: mut UiState, uitranslate_st: mut UitranslateState) { let b = new UiBackend b.text = fn rec_text - ui_backend(b) - ui_translator(fn tr) - ui_clock(fn clock) - ui_load_text(PAGE, "t.xml") - let root: UiNode = ui_nodes("t", null) - ui_place(root, 0.0, 0.0, 400.0, 400.0) + ui_backend(ui_st, b) + ui_translator(ui_st, fn tr) + ui_clock(ui_st, fn clock) + ui_load_text(ui_st, PAGE, "t.xml") + let root: UiNode = ui_nodes(ui_st, "t", null) + ui_place(ui_st, root, 0.0, 0.0, 400.0, 400.0) let texts = `{root.children[0].text} {root.children[1].text} {root.children[2].children[0].text} {root.children[2].children[1].text}` - print(`{texts} | {tip(root, 3)} | {tip(root, 4)} | {tip(root, 5)}`) + print(`{texts} | {tip(ui_st, uitranslate_st, root, 3)} | {tip(ui_st, uitranslate_st, root, 4)} | {tip(ui_st, uitranslate_st, root, 5)}`) } } diff --git a/examples/library/ui_zoom.ludic b/examples/library/ui_zoom.ludic index 923a8a1a..0cdd46f6 100644 --- a/examples/library/ui_zoom.ludic +++ b/examples/library/ui_zoom.ludic @@ -6,12 +6,12 @@ program UiZoom { numbers float const STYLE: string = "" const BODY: string = "

a

b

" - entry { - ui_backend(new UiBackend) - ui_load_text("" + STYLE + BODY + "", "z.xml") - ui_show("z", null, 0.0, 0.0, 400.0, 600.0) - let r: UiNode = ui_nodes("z", null) - ui_place(r, 0.0, 0.0, 400.0, 600.0) + entry (ui_st: mut UiState) { + ui_backend(ui_st, new UiBackend) + ui_load_text(ui_st, "" + STYLE + BODY + "", "z.xml") + ui_show(ui_st, "z", null, 0.0, 0.0, 400.0, 600.0) + let r: UiNode = ui_nodes(ui_st, "z", null) + ui_place(ui_st, r, 0.0, 0.0, 400.0, 600.0) let plain = r.children[0] let half = r.children[1] let inner = half.children[1].children[0] diff --git a/examples/modules/bank/ledger.ludic b/examples/modules/bank/ledger.ludic index 24cb4d30..4c86821a 100644 --- a/examples/modules/bank/ledger.ludic +++ b/examples/modules/bank/ledger.ludic @@ -1,13 +1,15 @@ # bank/ledger.ludic - inherits `module bank` from the barrel that imports it -var balance: int = 0 -export event Deposited { amount: int } -function add(n: int) -> void { - balance += n +export state BankState { + balance: int = 0 } -export function deposit(n: int) -> void { - add(n) +export event Deposited { amount: int } +function add(bank_st: mut BankState, n: int) -> void { + bank_st.balance += n +} +export function deposit(bank_st: mut BankState, n: int) -> void { + add(bank_st, n) emit Deposited(amount: n) } -export function total() -> int { - return balance +export function total(bank_st: BankState) -> int { + return bank_st.balance } diff --git a/examples/modules/friend_of.ludic b/examples/modules/friend_of.ludic index a5de3846..2fa5942c 100644 --- a/examples/modules/friend_of.ludic +++ b/examples/modules/friend_of.ludic @@ -5,8 +5,8 @@ import "bank" program FriendOf { friend module lab of bank - entry { - add(7) - print(`{balance} {total()}`) + entry (bank_st: mut BankState) { + add(bank_st, 7) + print(`{bank_st.balance} {total(bank_st)}`) } } diff --git a/examples/modules/kinds/fishing/index.ludic b/examples/modules/kinds/fishing/index.ludic index e8b3ef2c..518ad6ab 100644 --- a/examples/modules/kinds/fishing/index.ludic +++ b/examples/modules/kinds/fishing/index.ludic @@ -4,17 +4,19 @@ property Catch { fish: int = 0 } event Landed { fish: int } -var landed: int = 0 -var last: Catch = null -@On(Landed) handler Count { landed += fish } +export state FishingState { + landed: int = 0 + last: Catch = null +} +@On(Landed) handler Count(fishing_st: mut FishingState) { fishing_st.landed += fish } function catch_new(n: int) -> Catch { let c = new Catch c.fish = n return c } -export function fishing_day() -> int { - last = catch_new(3) - emit Landed(fish: last.fish) +export function fishing_day(fishing_st: mut FishingState) -> int { + fishing_st.last = catch_new(3) + emit Landed(fish: fishing_st.last.fish) emit Landed(fish: 4) - return landed + return fishing_st.landed } diff --git a/examples/modules/kinds/hunting/index.ludic b/examples/modules/kinds/hunting/index.ludic index a7a73d5b..ba9a9b35 100644 --- a/examples/modules/kinds/hunting/index.ludic +++ b/examples/modules/kinds/hunting/index.ludic @@ -5,11 +5,13 @@ property Catch { weight: float = 0.0 } event Landed { name: string } -var heard: string = "" -@On(Landed) handler Hear { heard = heard + name } -export function hunting_day() -> string { +export state HuntingState { + heard: string = "" +} +@On(Landed) handler Hear(hunting_st: mut HuntingState) { hunting_st.heard = hunting_st.heard + name } +export function hunting_day(hunting_st: HuntingState) -> string { let c = new Catch c.name = "hare" emit Landed(name: c.name) - return heard + return hunting_st.heard } diff --git a/examples/modules/port_vars.ludic b/examples/modules/port_vars.ludic index ed0f1605..fd5cd1ee 100644 --- a/examples/modules/port_vars.ludic +++ b/examples/modules/port_vars.ludic @@ -8,15 +8,17 @@ program PortVars { money: fn() -> int price: fn(int) -> int } - var g_money: int = 40 - var g_rate: int = 3 - function priced(n: int) -> int { - return n * g_rate + state PortvarsState { + g_money: int = 40 + g_rate: int = 3 } - bind Purse { money: g_money, price: fn priced } - entry { + function priced(portvars_st: PortvarsState, n: int) -> int { + return n * portvars_st.g_rate + } + bind Purse { money: PortvarsState.g_money, price: fn priced } + entry (portvars_st: mut PortvarsState) { let a = Purse.money() - g_money += 5 + portvars_st.g_money += 5 print(`{a} {Purse.money()} {Purse.price(4)}`) } } diff --git a/examples/modules/ports.ludic b/examples/modules/ports.ludic index f8d640a5..5520ef17 100644 --- a/examples/modules/ports.ludic +++ b/examples/modules/ports.ludic @@ -7,9 +7,9 @@ import "station/units" import "station/clock" import "station/app" program Ports { - entry { + entry (app_st: mut AppState) { let a = Clock.now() - advance(-25) + advance(app_st, -25) print(`{a} {hour_of_day()}`) } } diff --git a/examples/modules/private_kinds.ludic b/examples/modules/private_kinds.ludic index 91280678..f3247887 100644 --- a/examples/modules/private_kinds.ludic +++ b/examples/modules/private_kinds.ludic @@ -6,7 +6,7 @@ import "kinds/fishing" import "kinds/hunting" program PrivateKinds { - entry { - print(`{fishing_day()} {hunting_day()}`) + entry (fishing_st: mut FishingState, hunting_st: HuntingState) { + print(`{fishing_day(fishing_st)} {hunting_day(hunting_st)}`) } } diff --git a/examples/modules/private_twins.ludic b/examples/modules/private_twins.ludic index c87ce4fe..01f1a219 100644 --- a/examples/modules/private_twins.ludic +++ b/examples/modules/private_twins.ludic @@ -9,7 +9,7 @@ program PrivateTwins { function seed() -> int { return 7 } - entry { - print(`{shop_seed()} {weather_seed()} {seed()}`) + entry (shop_st: ShopState, weather_st: WeatherState) { + print(`{shop_seed(shop_st)} {weather_seed(weather_st)} {seed()}`) } } diff --git a/examples/modules/station/app/index.ludic b/examples/modules/station/app/index.ludic index 568e24f9..982eec93 100644 --- a/examples/modules/station/app/index.ludic +++ b/examples/modules/station/app/index.ludic @@ -1,10 +1,12 @@ # app/index.ludic - the app puts the program together: it sees the port and fills it module app uses clock bind Clock { now: fn game_hours } -var g_hours: int = 30 -function game_hours() -> int { - return g_hours +export state AppState { + g_hours: int = 30 } -export function advance(h: int) -> void { - g_hours += h +function game_hours(app_st: AppState) -> int { + return app_st.g_hours +} +export function advance(app_st: mut AppState, h: int) -> void { + app_st.g_hours += h } diff --git a/examples/modules/tangle.ludic b/examples/modules/tangle.ludic index c21888ea..c019dbfd 100644 --- a/examples/modules/tangle.ludic +++ b/examples/modules/tangle.ludic @@ -7,8 +7,8 @@ import "tangle/c" import "tangle/a" import "tangle/b" program Tangle { - entry { - a_bump() - print(a_run() + b_rec.n - 1) + entry (b_st: mut BState) { + a_bump(b_st) + print(a_run(b_st) + b_st.b_rec.n - 1) } } diff --git a/examples/modules/tangle/a/index.ludic b/examples/modules/tangle/a/index.ludic index f74836e1..7107561d 100644 --- a/examples/modules/tangle/a/index.ludic +++ b/examples/modules/tangle/a/index.ludic @@ -1,13 +1,13 @@ # a/index.ludic - uses b, and writes b's global from outside it module a -export function a_run() -> int { - b_count = b_count + 1 +export function a_run(b_st: mut BState) -> int { + b_st.b_count = b_st.b_count + 1 return b_run() + c_one() } export function a_two() -> int { return 2 } -export function a_bump() -> void { - let r = b_rec +export function a_bump(b_st: mut BState) -> void { + let r = b_st.b_rec r.n = r.n + 1 } diff --git a/examples/modules/tangle/b/index.ludic b/examples/modules/tangle/b/index.ludic index 67a925f5..9d4501f8 100644 --- a/examples/modules/tangle/b/index.ludic +++ b/examples/modules/tangle/b/index.ludic @@ -1,10 +1,12 @@ # b/index.ludic - uses a back: a and b are a cycle module b -export var b_count: int = 0 +export state BState { + b_count: int = 0 + b_rec: Tally = new Tally +} export property Tally { n: int = 0 } -export var b_rec: Tally = new Tally export function b_run() -> int { return a_two() + c_one() } diff --git a/examples/modules/twins/shop/index.ludic b/examples/modules/twins/shop/index.ludic index adc8e0ed..dac15388 100644 --- a/examples/modules/twins/shop/index.ludic +++ b/examples/modules/twins/shop/index.ludic @@ -1,10 +1,12 @@ # shop/index.ludic - private names a neighbour also uses: seed, dice, LIMIT module shop -var dice: int = 100 +export state ShopState { + dice: int = 100 +} const LIMIT: int = 5 -function seed() -> int { - return dice + LIMIT +function seed(shop_st: ShopState) -> int { + return shop_st.dice + LIMIT } -export function shop_seed() -> int { - return seed() +export function shop_seed(shop_st: ShopState) -> int { + return seed(shop_st) } diff --git a/examples/modules/twins/weather/index.ludic b/examples/modules/twins/weather/index.ludic index 560fe306..f0da9a29 100644 --- a/examples/modules/twins/weather/index.ludic +++ b/examples/modules/twins/weather/index.ludic @@ -1,13 +1,15 @@ # weather/index.ludic - the same private spellings, its own; a local named dice is its own too module weather -var dice: int = 20 +export state WeatherState { + dice: int = 20 +} const LIMIT: int = 2 -function seed() -> int { - return dice * LIMIT +function seed(weather_st: WeatherState) -> int { + return weather_st.dice * LIMIT } function roll(dice: int) -> int { return dice + 1 } -export function weather_seed() -> int { - return seed() + roll(0) +export function weather_seed(weather_st: WeatherState) -> int { + return seed(weather_st) + roll(0) } diff --git a/examples/modules/visible.ludic b/examples/modules/visible.ludic index 915a5b3b..73d27c61 100644 --- a/examples/modules/visible.ludic +++ b/examples/modules/visible.ludic @@ -4,11 +4,13 @@ # Running it prints: 42 42 import "bank" program Visible { - var heard: int = 0 - @On(Deposited) handler Heard { heard += amount } - entry { - deposit(30) - deposit(12) - print(`{total()} {heard}`) + state VisibleState { + heard: int = 0 + } + @On(Deposited) handler Heard(visible_st: mut VisibleState) { visible_st.heard += amount } + entry (bank_st: mut BankState, visible_st: VisibleState) { + deposit(bank_st, 30) + deposit(bank_st, 12) + print(`{total(bank_st)} {visible_st.heard}`) } } diff --git a/examples/rendering/gl_triangle.ludic b/examples/rendering/gl_triangle.ludic index ca88eaf2..b20ecab6 100644 --- a/examples/rendering/gl_triangle.ludic +++ b/examples/rendering/gl_triangle.ludic @@ -7,19 +7,21 @@ program GlTriangle { property Marker { on: int = 1 } model Anchor { Marker } - var prog: int = 0 - var vao: int = 0 - var frame: int = 0 + state GltriangleState { + prog: int = 0 + vao: int = 0 + frame: int = 0 + } const VS: string = "#version 410 core\nlayout(location=0) in vec2 p; layout(location=1) in vec3 c; out vec3 vc;\nvoid main(){ vc = c; gl_Position = vec4(p, 0.0, 1.0); }\n" const FS: string = "#version 410 core\nin vec3 vc; out vec4 o;\nvoid main(){ o = vec4(vc, 1.0); }\n" - handler Boot phase Start { + handler Boot(gltriangle_st: mut GltriangleState) phase Start { spawn Anchor {} if not Gl.open(width: 640, height: 360, title: "Ludic GL") { print("no GL context"); quit() } print(Gl.get_string(name: GL_VERSION)) - prog = Gl.program(vs: VS, fs: FS) - vao = Gl.vao() + gltriangle_st.prog = Gl.program(vs: VS, fs: FS) + gltriangle_st.vao = Gl.vao() let vbo = Gl.buffer() Gl.bind_buffer(target: GL_ARRAY_BUFFER, buffer: vbo) let v = Gl.floats(15) @@ -34,16 +36,16 @@ program GlTriangle { Gl.check(tag: "boot") } - handler Draw phase Render { + handler Draw(gltriangle_st: mut GltriangleState) phase Render { Gl.bind_framebuffer(target: GL_FRAMEBUFFER, framebuffer: Gl.screen_fbo()) Gl.viewport(x: 0, y: 0, width: Gl.width(), height: Gl.height()) Gl.clear_color(red: 0.1, green: 0.12, blue: 0.18, alpha: 1.0) Gl.clear(mask: GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - Gl.use_program(prog: prog) - Gl.bind_vertex_array(array: vao) + Gl.use_program(prog: gltriangle_st.prog) + Gl.bind_vertex_array(array: gltriangle_st.vao) Gl.draw_arrays(mode: GL_TRIANGLES, first: 0, count: 3) - frame += 1 - if frame == 1 { Gl.screenshot(path: "build/gl_triangle.ppm") } + gltriangle_st.frame += 1 + if gltriangle_st.frame == 1 { Gl.screenshot(path: "build/gl_triangle.ppm") } Gl.swap() } } diff --git a/examples/rendering/reload.ludic b/examples/rendering/reload.ludic index 46232ee1..f60f18b9 100644 --- a/examples/rendering/reload.ludic +++ b/examples/rendering/reload.ludic @@ -14,8 +14,8 @@ program Reload { # A program only links the GL runtime render3d stands on when it names Gl.* itself # (the parser's g_uses_gl), so even a check that draws nothing presents its frame. # render3d calls back into the program for what stands on the terrain - function scene_draw() -> void { scatter_draw() } - function scene_draw_casters(light_vp: floats) -> void { scatter_draw_casters(light_vp) } + function scene_draw(render3d_st: mut Render3dState) -> void { scatter_draw(render3d_st) } + function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { scatter_draw_casters(render3d_st, light_vp) } # no streamed cover here, but stream.ludic names the generator, so every program supplies one function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { } @@ -27,60 +27,60 @@ program Reload { if not cond { print(`reload: FAILED - {what}`) } return cond } - function populate() -> void { - let l = layer_cards(model_cross_card(), 1000, F_ZERO, fi(400)) + function populate(render3d_st: mut Render3dState) -> void { + let l = layer_cards(render3d_st, model_cross_card(render3d_st), 1000, F_ZERO, fi(400)) for i in 0 .. 400 { let x = fi(i * 2 - 400) - layer_add(l, x, terrain_height(x, fi(30)), fi(30), F_ONE, F_ZERO, F_ZERO, F_ZERO) - col_add(x, fi(30), F_ONE) + layer_add(l, x, terrain_height(render3d_st, x, fi(30)), fi(30), F_ONE, F_ZERO, F_ZERO, F_ZERO) + col_add(render3d_st, x, fi(30), F_ONE) } - col_build() + col_build(render3d_st) } - handler Boot phase Start { + handler Boot(render3d_st: mut Render3dState) phase Start { spawn Anchor {} - r3d_on_draw(fn scene_draw) - r3d_on_casters(fn scene_draw_casters) - r3d_on_stream_fill(fn stream_fill) + r3d_on_draw(render3d_st, fn scene_draw) + r3d_on_casters(render3d_st, fn scene_draw_casters) + r3d_on_stream_fill(render3d_st, fn stream_fill) TERRAIN_HALF = 1000 ter_smooth = true - if not r3d_init(640, 360, "Reload") { + if not r3d_init(render3d_st, 640, 360, "Reload") { quit() return } - cam_set(F_ZERO, fi(80), fi(300), fi(180), f_neg(fi(10))) - water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200)) - populate() + cam_set(render3d_st, F_ZERO, fi(80), fi(300), fi(180), f_neg(fi(10))) + water_init(render3d_st, fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200)) + populate(render3d_st) var ok = true ok = check("the first map has layers and colliders", len(sc_layers) == 1 and col_n == 400) and ok # --- swap to a 3 km map with a sea and a lake above it - scatter_clear_all() - actor_clear_all() - col_reset() - water_bodies_clear() - terrain_lake(F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO) - terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1500) + scatter_clear_all(render3d_st) + actor_clear_all(render3d_st) + col_reset(render3d_st) + water_bodies_clear(render3d_st) + terrain_lake(render3d_st, F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO) + terrain_reload(render3d_st, "", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1500) ok = check("everything of the first map released", len(sc_layers) == 0 and col_n == 0 and wb_n == 0 and not water_on) and ok ok = check("the new half", TERRAIN_HALF == 1500 and ter_heights != null and ter_height_tex != 0) and ok ok = check("patch bounds rebuilt", cd_min != null and len(cd_min) == CD_LEVELS) and ok - ok = check("patches scale with the map", cd_size(0) == f_mul(fi(32), fr(3000, 8192))) and ok - terrain_height(F_ZERO, F_ZERO) + ok = check("patches scale with the map", cd_size(render3d_st, 0) == f_mul(fi(32), fr(3000, 8192))) and ok + terrain_height(render3d_st, F_ZERO, F_ZERO) ok = check("the cached height scale is the new map's", ter_h_scale == fr(TERRAIN_RES, 3000)) and ok - let sea = water_body_add(F_ZERO, F_ZERO, F_ZERO, fi(20000), fi(20000), true) - let lake = water_body_add(fl(12.0), fi(600), fi(-400), fi(150), fi(100), false) + let sea = water_body_add(render3d_st, F_ZERO, F_ZERO, F_ZERO, fi(20000), fi(20000), true) + let lake = water_body_add(render3d_st, fl(12.0), fi(600), fi(-400), fi(150), fi(100), false) ok = check("two water bodies, the sea mirrored", sea == 0 and lake == 1 and wb_n == 2 and wb_primary == 0 and water_level == F_ZERO and water_on) and ok - populate() + populate(render3d_st) ok = check("the new map takes layers and colliders", len(sc_layers) == 1 and col_n == 400 and col_side == 3000 / COL_CELL) and ok # a sea below the carved lake: set once, and not undone by a reload - terrain_sea(fl(-2.0)) - terrain_lake(fl(12.0), fi(600), fi(-400), fi(150), fi(100)) - ok = check("the sea is its own level", ter_sea_set and ter_sea_gen() == fl(-2.0) and ter_lake_level == fl(12.0)) and ok + terrain_sea(render3d_st, fl(-2.0)) + terrain_lake(render3d_st, fl(12.0), fi(600), fi(-400), fi(150), fi(100)) + ok = check("the sea is its own level", ter_sea_set and ter_sea_gen(render3d_st) == fl(-2.0) and ter_lake_level == fl(12.0)) and ok # --- and back: the swap is repeatable - scatter_clear_all() - col_reset() - water_init(fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200)) - terrain_reload("", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1000) - populate() + scatter_clear_all(render3d_st) + col_reset(render3d_st) + water_init(render3d_st, fl(4.0), F_ZERO, F_ZERO, fi(200), fi(200)) + terrain_reload(render3d_st, "", F_ZERO, F_ZERO, F_ZERO, F_ZERO, F_ZERO, "", 1000) + populate(render3d_st) ok = check("back to 2 km", TERRAIN_HALF == 1000 and len(cd_min) == CD_LEVELS and wb_n == 1 and col_side == 2000 / COL_CELL) and ok if ok { print("RELOAD OK") } else { print("RELOAD FAILED") } quit() diff --git a/examples/rendering/smooth.ludic b/examples/rendering/smooth.ludic index 5830790a..9a795e80 100644 --- a/examples/rendering/smooth.ludic +++ b/examples/rendering/smooth.ludic @@ -13,17 +13,19 @@ program Smooth { property Marker { on: int = 1 } model Anchor { Marker } - var frame: int = 0 - var shot_at: int = 40 - var walk: bool = false - var spin: bool = false - var l_blades: Layer = null - var l_grass_a: Layer = null - var l_grass_b: Layer = null - var s_blades: Stream = null - var s_cards_a: Stream = null - var s_cards_b: Stream = null - var l_trees: Layer = null + state SmoothState { + frame: int = 0 + shot_at: int = 40 + walk: bool = false + spin: bool = false + l_blades: Layer = null + l_grass_a: Layer = null + l_grass_b: Layer = null + s_blades: Stream = null + s_cards_a: Stream = null + s_cards_b: Stream = null + l_trees: Layer = null + } function rnd() -> float { return float(rng_range(0, 9999)) / 10000.0 } function rnd_range(a: float, b: float) -> float { return Math.lerp(a, b, rnd()) } @@ -31,10 +33,10 @@ program Smooth { let t = Math.clamp((x - a) / (b - a), 0.0, 1.0) return t * t * (3.0 - 2.0 * t) } - function slope_at(x: float, z: float) -> float { + function slope_at(render3d_st: mut Render3dState, x: float, z: float) -> float { let e = 2.0 - let dx = terrain_height(x + e, z) - terrain_height(x - e, z) - let dz = terrain_height(x, z + e) - terrain_height(x, z - e) + let dx = terrain_height(render3d_st, x + e, z) - terrain_height(render3d_st, x - e, z) + let dz = terrain_height(render3d_st, x, z + e) - terrain_height(render3d_st, x, z - e) let ny = 4.0 / Math.sqrt(dx * dx + dz * dz + 16.0) return 1.0 - ny } @@ -44,7 +46,7 @@ program Smooth { } # grass only where the ground is grassy: gentle, above the water, below the mountain - function stream_fill(s: Stream, cx: int, cz: int, band: int) -> void { + function stream_fill(render3d_st: mut Render3dState, s: Stream, cx: int, cz: int, band: int) -> void { seed((cx * 73856093) ^ (cz * 19349663) ^ (band * 83492791) ^ (s.kind * 2654435761)) let size = s.size let x0 = float(cx) * size; let z0 = float(cz) * size @@ -61,19 +63,19 @@ program Smooth { while x < x0 + size { let px = x + rnd() * step let pz = z + rnd() * step - let h = terrain_height(px, pz) + let h = terrain_height(render3d_st, px, pz) # Grassy ground only: above the water, off the steep parts, below the rim. Every # test is a smooth ramp — a hard height cut carves the cover into contour rings, # because the cut lands on a line of constant elevation. var keep = smooth(0.2, 1.6, h) # out of the water keep = keep * smooth(90.0, 45.0, h) # below the mountain - keep = keep * smooth(0.42, 0.16, slope_at(px, pz)) + keep = keep * smooth(0.42, 0.16, slope_at(render3d_st, px, pz)) keep = keep * (0.25 + 0.9 * noise01(px, pz, 0.02)) keep = keep * 0.75 var sc = rnd_range(0.16, 0.4) if s.kind != 0 { sc = rnd_range(1.5, 2.5) * (1.0 + 0.5 * float(band)) } if rnd() < keep { - stream_emit(s, px, h - 0.03, pz, sc, rnd() * (2.0 * PI), rnd(), rnd_range(0.6, 1.0)) + stream_emit(render3d_st, s, px, h - 0.03, pz, sc, rnd() * (2.0 * PI), rnd(), rnd_range(0.6, 1.0)) } x = x + step } @@ -81,24 +83,24 @@ program Smooth { } } - function scene_draw() -> void { scatter_draw() } - function scene_draw_casters(light_vp: floats) -> void { scatter_draw_casters(light_vp) } + function scene_draw(render3d_st: mut Render3dState) -> void { scatter_draw(render3d_st) } + function scene_draw_casters(render3d_st: mut Render3dState, light_vp: floats) -> void { scatter_draw_casters(render3d_st, light_vp) } - handler Boot phase Start { + handler Boot(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Start { spawn Anchor {} - r3d_on_draw(fn scene_draw) - r3d_on_casters(fn scene_draw_casters) - r3d_on_stream_fill(fn stream_fill) + r3d_on_draw(render3d_st, fn scene_draw) + r3d_on_casters(render3d_st, fn scene_draw_casters) + r3d_on_stream_fill(render3d_st, fn stream_fill) # 2 km square, generated analytically TERRAIN_HALF = 1000 ter_smooth = true - if not r3d_init(1920, 1080, "Smooth") { + if not r3d_init(render3d_st, 1920, 1080, "Smooth") { quit() return } - walk = Os.has_env("R3D_WALK") - spin = Os.has_env("R3D_SPIN") - if Os.has_env("R3D_SHOT") { shot_at = Text.to_int(Os.env("R3D_SHOT")) } + smooth_st.walk = Os.has_env("R3D_WALK") + smooth_st.spin = Os.has_env("R3D_SPIN") + if Os.has_env("R3D_SHOT") { smooth_st.shot_at = Text.to_int(Os.env("R3D_SHOT")) } var cx = 0.0; var cz = 260.0 if Os.has_env("R3D_CAM_X") { cx = float(Text.to_int(Os.env("R3D_CAM_X"))) } @@ -107,54 +109,54 @@ program Smooth { if Os.has_env("R3D_CAM_H") { ch = float(Text.to_int(Os.env("R3D_CAM_H"))) } if Os.has_env("R3D_CAM_PITCH") { cp = float(Text.to_int(Os.env("R3D_CAM_PITCH"))) } if Os.has_env("R3D_CAM_YAW") { cy = float(Text.to_int(Os.env("R3D_CAM_YAW"))) } - cam_set(cx, terrain_height(cx, cz) + ch, cz, cy, cp) + cam_set(render3d_st, cx, terrain_height(render3d_st, cx, cz) + ch, cz, cy, cp) # a small pond in the middle of the meadow — the plane self-clips to the basin, so # its extent only has to cover the hollow, not the map - water_init(4.0, 0.0, 0.0, 200.0, 200.0) - sky_set_yaw(Math.deg_to_rad(120.0)) + water_init(render3d_st, 4.0, 0.0, 0.0, 200.0, 200.0) + sky_set_yaw(render3d_st, Math.deg_to_rad(120.0)) # grass: blades underfoot, cards beyond let dir = r3d_assets + "/models/grass_medium_01" - let ga = gltf_load(dir, "grass_medium_01_1k.gltf", "grass_medium_01_tall_a_LOD0") - let gb = gltf_load(dir, "grass_medium_01_1k.gltf", "grass_medium_01_mid_a_LOD0") + let ga = gltf_load(render3d_st, dir, "grass_medium_01_1k.gltf", "grass_medium_01_tall_a_LOD0") + let gb = gltf_load(render3d_st, dir, "grass_medium_01_1k.gltf", "grass_medium_01_mid_a_LOD0") if ga == null or gb == null { print("smooth: no grass models"); return } # blades are the expensive layer: keep them near, and cap them low - l_blades = layer_new(model_blade(), 400000, true, 3.0, 0.0, 70.0) - l_blades.blade = true - l_grass_a = layer_cards(ga, 200000, 0.3, 260.0) - l_grass_b = layer_cards(gb, 200000, 0.3, 260.0) - v3_set(l_grass_a.tint, 0.95, 1.0, 0.85) - v3_set(l_grass_b.tint, 0.9, 0.98, 0.8) - l_grass_a.rough = 1.6; l_grass_b.rough = 1.6 - s_blades = stream_new(l_blades, 8.0, 60.0, 12.0, 28.0, 60.0, 60.0) - s_cards_a = stream_new(l_grass_a, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0) - s_cards_b = stream_new(l_grass_b, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0) - s_blades.kind = 0; s_cards_a.kind = 1; s_cards_b.kind = 2 - place_trees() + smooth_st.l_blades = layer_new(render3d_st, model_blade(render3d_st), 400000, true, 3.0, 0.0, 70.0) + smooth_st.l_blades.blade = true + smooth_st.l_grass_a = layer_cards(render3d_st, ga, 200000, 0.3, 260.0) + smooth_st.l_grass_b = layer_cards(render3d_st, gb, 200000, 0.3, 260.0) + v3_set(smooth_st.l_grass_a.tint, 0.95, 1.0, 0.85) + v3_set(smooth_st.l_grass_b.tint, 0.9, 0.98, 0.8) + smooth_st.l_grass_a.rough = 1.6; smooth_st.l_grass_b.rough = 1.6 + smooth_st.s_blades = stream_new(render3d_st, smooth_st.l_blades, 8.0, 60.0, 12.0, 28.0, 60.0, 60.0) + smooth_st.s_cards_a = stream_new(render3d_st, smooth_st.l_grass_a, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0) + smooth_st.s_cards_b = stream_new(render3d_st, smooth_st.l_grass_b, 32.0, 240.0, 45.0, 110.0, 240.0, 240.0) + smooth_st.s_blades.kind = 0; smooth_st.s_cards_a.kind = 1; smooth_st.s_cards_b.kind = 2 + place_trees(render3d_st, smooth_st) } # scattered firs on the gentle ground, thinning toward the pond and the rim - function place_trees() -> void { - let model = gltf_load(r3d_assets + "/models/fir_tree_01", "fir_tree_01_1k.gltf", "fir_tree_01_c_LOD0") + function place_trees(render3d_st: mut Render3dState, smooth_st: mut SmoothState) -> void { + let model = gltf_load(render3d_st, r3d_assets + "/models/fir_tree_01", "fir_tree_01_1k.gltf", "fir_tree_01_c_LOD0") if model == null { print("smooth: no fir model"); return } - l_trees = layer_new(model, 20000, false, 0.0, 90.0, 0.0) - layer_set_impostor(l_trees, impostor_bake(model, 12, 512, 1024)) - v3_set(l_trees.tint, 0.9, 1.0, 0.85) + smooth_st.l_trees = layer_new(render3d_st, model, 20000, false, 0.0, 90.0, 0.0) + layer_set_impostor(render3d_st, smooth_st.l_trees, impostor_bake(render3d_st, model, 12, 512, 1024)) + v3_set(smooth_st.l_trees.tint, 0.9, 1.0, 0.85) seed(4242) var z = -900.0 while z < 900.0 { var x = -900.0 while x < 900.0 { let px = x + rnd() * 14.0; let pz = z + rnd() * 14.0 - let h = terrain_height(px, pz) + let h = terrain_height(render3d_st, px, pz) var keep = smooth(7.0, 14.0, h) # above the pond shore keep = keep * smooth(120.0, 60.0, h) # below the rim - keep = keep * smooth(0.45, 0.2, slope_at(px, pz)) + keep = keep * smooth(0.45, 0.2, slope_at(render3d_st, px, pz)) keep = keep * smooth(0.45, 0.75, noise01(px, pz, 0.004)) if rnd() < keep * 0.5 { let sc = rnd_range(0.7, 1.35) - layer_add(l_trees, px, h - 0.2, pz, sc, rnd() * (2.0 * PI), rnd(), 0.0) + layer_add(smooth_st.l_trees, px, h - 0.2, pz, sc, rnd() * (2.0 * PI), rnd(), 0.0) } x = x + 14.0 } @@ -162,9 +164,9 @@ program Smooth { } } - handler Fly phase Input { - if walk { cam_move(1.0, 0.0, 0.0, 0.003, 0.0); return } - if spin { cam_move(0.0, 0.0, 0.0, 0.02, 0.0); return } + handler Fly(render3d_st: mut Render3dState, smooth_st: SmoothState) phase Input { + if smooth_st.walk { cam_move(render3d_st, 1.0, 0.0, 0.0, 0.003, 0.0); return } + if smooth_st.spin { cam_move(render3d_st, 0.0, 0.0, 0.0, 0.02, 0.0); return } if not is_windowed() { return } Input.poll() var fwd = 0.0; var side = 0.0; var up = 0.0 @@ -180,17 +182,17 @@ program Smooth { dyaw = float(Input.mouse_dx()) * -0.004 dpitch = float(Input.mouse_dy()) * -0.004 } - if fwd != 0 or side != 0 or up != 0 or dyaw != 0 or dpitch != 0 { cam_move(fwd, side, up, dyaw, dpitch) } + if fwd != 0 or side != 0 or up != 0 or dyaw != 0 or dpitch != 0 { cam_move(render3d_st, fwd, side, up, dyaw, dpitch) } if Input.key_pressed(key: 'p') { - let gy = terrain_height(cam_pos[0], cam_pos[2]) + let gy = terrain_height(render3d_st, cam_pos[0], cam_pos[2]) print(`R3D_CAM_X={string(int(cam_pos[0]))} R3D_CAM_Z={string(int(cam_pos[2]))} R3D_CAM_H={string(int(cam_pos[1] - gy))} R3D_CAM_YAW={string(int(cam_yaw * (180.0 / PI)))} R3D_CAM_PITCH={string(int(cam_pitch * (180.0 / PI)))}`) } } - handler Draw phase Render { - r3d_frame(float(Time.elapsed())) - frame += 1 - if frame == shot_at { Gl.screenshot(path: "build/smooth.ppm") } + handler Draw(render3d_st: mut Render3dState, smooth_st: mut SmoothState) phase Render { + r3d_frame(render3d_st, float(Time.elapsed())) + smooth_st.frame += 1 + if smooth_st.frame == smooth_st.shot_at { Gl.screenshot(path: "build/smooth.ppm") } Gl.swap() } } diff --git a/examples/rendering/vk_compute.ludic b/examples/rendering/vk_compute.ludic index afec3c62..6197a720 100644 --- a/examples/rendering/vk_compute.ludic +++ b/examples/rendering/vk_compute.ludic @@ -20,7 +20,10 @@ program VkCompute { const W: int = 640 const H: int = 360 - var dev: pointer = null + state VkcomputeState { + dev: pointer = null + spv_len: int = 0 + } unsafe function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() } unsafe function handle(out: bytes) -> long { return vk_get_i64(out, 0) } @@ -30,7 +33,7 @@ program VkCompute { } return false } - unsafe function read_all(path: string) -> bytes { + unsafe function read_all(vkcompute_st: mut VkcomputeState, path: string) -> bytes { let f = file_open(path, "rb") if f == null { return null } file_seek(f, 0, 2) @@ -39,16 +42,15 @@ program VkCompute { let b = bytes(n + 4) file_read(f, b, n) file_close(f) - spv_len = n + vkcompute_st.spv_len = n return b } - var spv_len: int = 0 - handler Boot phase Start { + handler Boot(vkcompute_st: mut VkcomputeState) phase Start { spawn Anchor {} var spv_path = "build/vk_compute.spv" if Os.has_env("VKC_SPV") { spv_path = Os.env("VKC_SPV") } - let spv = read_all(spv_path) + let spv = read_all(vkcompute_st, spv_path) if spv == null { print(`vulkan: no SPIR-V at {spv_path} (compile vk_compute.slang with slangc)`); quit() } if Vk.open() == 0 { print("vulkan: no loader"); quit() } @@ -126,8 +128,8 @@ program VkCompute { } r = vk_create_device(pd, dci, null, out) if r != VK_SUCCESS { fail("vkCreateDevice", r) } - dev = vk_get_ptr(out, 0) - vk_get_device_queue(dev, family, 0, out) + vkcompute_st.dev = vk_get_ptr(out, 0) + vk_get_device_queue(vkcompute_st.dev, family, 0, out) let queue = vk_get_ptr(out, 0) # ---- a host-visible storage buffer for the picture @@ -138,11 +140,11 @@ program VkCompute { vk_put_i64(bci, VkBufferCreateInfo_size, nbytes) vk_put_i32(bci, VkBufferCreateInfo_usage, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) vk_put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE) - r = vk_create_buffer(dev, bci, null, out) + r = vk_create_buffer(vkcompute_st.dev, bci, null, out) if r != VK_SUCCESS { fail("vkCreateBuffer", r) } let buf = handle(out) let req = bytes(VkMemoryRequirements_sizeof) - vk_get_buffer_memory_requirements(dev, buf, req) + vk_get_buffer_memory_requirements(vkcompute_st.dev, buf, req) let mp = bytes(VkPhysicalDeviceMemoryProperties_sizeof) vk_get_physical_device_memory_properties(pd, mp) let want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT @@ -158,21 +160,21 @@ program VkCompute { vk_put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO) vk_put_i64(mai, VkMemoryAllocateInfo_allocationSize, vk_get_i64(req, VkMemoryRequirements_size)) vk_put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, mtype) - r = vk_allocate_memory(dev, mai, null, out) + r = vk_allocate_memory(vkcompute_st.dev, mai, null, out) if r != VK_SUCCESS { fail("vkAllocateMemory", r) } let mem = handle(out) let zero: long = 0 - r = vk_bind_buffer_memory(dev, buf, mem, zero) + r = vk_bind_buffer_memory(vkcompute_st.dev, buf, mem, zero) if r != VK_SUCCESS { fail("vkBindBufferMemory", r) } # ---- the shader, its layout, the pipeline let smci = bytes(VkShaderModuleCreateInfo_sizeof) vk_zero(smci, VkShaderModuleCreateInfo_sizeof) vk_put_i32(smci, VkShaderModuleCreateInfo_sType, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) - let code_size: long = spv_len + let code_size: long = vkcompute_st.spv_len vk_put_i64(smci, VkShaderModuleCreateInfo_codeSize, code_size) vk_put_ptr(smci, VkShaderModuleCreateInfo_pCode, spv) - r = vk_create_shader_module(dev, smci, null, out) + r = vk_create_shader_module(vkcompute_st.dev, smci, null, out) if r != VK_SUCCESS { fail("vkCreateShaderModule", r) } let module = handle(out) @@ -188,7 +190,7 @@ program VkCompute { vk_put_i32(dslci, VkDescriptorSetLayoutCreateInfo_bindingCount, 1) vk_put_ptr(dslci, VkDescriptorSetLayoutCreateInfo_pBindings, binding) let set_layouts = bytes(8) - r = vk_create_descriptor_set_layout(dev, dslci, null, set_layouts) + r = vk_create_descriptor_set_layout(vkcompute_st.dev, dslci, null, set_layouts) if r != VK_SUCCESS { fail("vkCreateDescriptorSetLayout", r) } let pcr = bytes(VkPushConstantRange_sizeof) @@ -202,7 +204,7 @@ program VkCompute { vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pSetLayouts, set_layouts) vk_put_i32(plci, VkPipelineLayoutCreateInfo_pushConstantRangeCount, 1) vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pPushConstantRanges, pcr) - r = vk_create_pipeline_layout(dev, plci, null, out) + r = vk_create_pipeline_layout(vkcompute_st.dev, plci, null, out) if r != VK_SUCCESS { fail("vkCreatePipelineLayout", r) } let layout = handle(out) @@ -215,7 +217,7 @@ program VkCompute { vk_put_i64(cpci, st + VkPipelineShaderStageCreateInfo_module, module) vk_put_ptr(cpci, st + VkPipelineShaderStageCreateInfo_pName, "main") vk_put_i64(cpci, VkComputePipelineCreateInfo_layout, layout) - r = vk_create_compute_pipelines(dev, zero, 1, cpci, null, out) + r = vk_create_compute_pipelines(vkcompute_st.dev, zero, 1, cpci, null, out) if r != VK_SUCCESS { fail("vkCreateComputePipelines", r) } let pipeline = handle(out) @@ -229,7 +231,7 @@ program VkCompute { vk_put_i32(dpci, VkDescriptorPoolCreateInfo_maxSets, 1) vk_put_i32(dpci, VkDescriptorPoolCreateInfo_poolSizeCount, 1) vk_put_ptr(dpci, VkDescriptorPoolCreateInfo_pPoolSizes, psz) - r = vk_create_descriptor_pool(dev, dpci, null, out) + r = vk_create_descriptor_pool(vkcompute_st.dev, dpci, null, out) if r != VK_SUCCESS { fail("vkCreateDescriptorPool", r) } let pool = handle(out) let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof) @@ -239,7 +241,7 @@ program VkCompute { vk_put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1) vk_put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, set_layouts) let sets = bytes(8) - r = vk_allocate_descriptor_sets(dev, dsai, sets) + r = vk_allocate_descriptor_sets(vkcompute_st.dev, dsai, sets) if r != VK_SUCCESS { fail("vkAllocateDescriptorSets", r) } let dbi = bytes(VkDescriptorBufferInfo_sizeof) vk_put_i64(dbi, VkDescriptorBufferInfo_buffer, buf) @@ -253,14 +255,14 @@ program VkCompute { vk_put_i32(wds, VkWriteDescriptorSet_descriptorCount, 1) vk_put_i32(wds, VkWriteDescriptorSet_descriptorType, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) vk_put_ptr(wds, VkWriteDescriptorSet_pBufferInfo, dbi) - vk_update_descriptor_sets(dev, 1, wds, 0, null) + vk_update_descriptor_sets(vkcompute_st.dev, 1, wds, 0, null) # ---- record, submit, wait let cpi = bytes(VkCommandPoolCreateInfo_sizeof) vk_zero(cpi, VkCommandPoolCreateInfo_sizeof) vk_put_i32(cpi, VkCommandPoolCreateInfo_sType, VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO) vk_put_i32(cpi, VkCommandPoolCreateInfo_queueFamilyIndex, family) - r = vk_create_command_pool(dev, cpi, null, out) + r = vk_create_command_pool(vkcompute_st.dev, cpi, null, out) if r != VK_SUCCESS { fail("vkCreateCommandPool", r) } let cmd_pool = handle(out) let cbai = bytes(VkCommandBufferAllocateInfo_sizeof) @@ -270,7 +272,7 @@ program VkCompute { vk_put_i32(cbai, VkCommandBufferAllocateInfo_level, VK_COMMAND_BUFFER_LEVEL_PRIMARY) vk_put_i32(cbai, VkCommandBufferAllocateInfo_commandBufferCount, 1) let cbs = bytes(8) - r = vk_allocate_command_buffers(dev, cbai, cbs) + r = vk_allocate_command_buffers(vkcompute_st.dev, cbai, cbs) if r != VK_SUCCESS { fail("vkAllocateCommandBuffers", r) } let cb = vk_get_ptr(cbs, 0) let cbbi = bytes(VkCommandBufferBeginInfo_sizeof) @@ -294,7 +296,7 @@ program VkCompute { vk_zero(fci, VkFenceCreateInfo_sizeof) vk_put_i32(fci, VkFenceCreateInfo_sType, VK_STRUCTURE_TYPE_FENCE_CREATE_INFO) let fences = bytes(8) - r = vk_create_fence(dev, fci, null, fences) + r = vk_create_fence(vkcompute_st.dev, fci, null, fences) if r != VK_SUCCESS { fail("vkCreateFence", r) } let si = bytes(VkSubmitInfo_sizeof) vk_zero(si, VkSubmitInfo_sizeof) @@ -304,13 +306,13 @@ program VkCompute { r = vk_queue_submit(queue, 1, si, vk_get_i64(fences, 0)) if r != VK_SUCCESS { fail("vkQueueSubmit", r) } let forever: long = -1 - r = vk_wait_for_fences(dev, 1, fences, 1, forever) + r = vk_wait_for_fences(vkcompute_st.dev, 1, fences, 1, forever) if r != VK_SUCCESS { fail("vkWaitForFences", r) } print(`vulkan: dispatched {(W + 15) / 16}x{(H + 15) / 16} groups and waited for the fence`) # ---- read the picture back let pp = bytes(8) - r = vk_map_memory(dev, mem, zero, nbytes, 0, pp) + r = vk_map_memory(vkcompute_st.dev, mem, zero, nbytes, 0, pp) if r != VK_SUCCESS { fail("vkMapMemory", r) } let px = vk_get_ptr(pp, 0) var out_path = "build/vk_compute.ppm" @@ -329,19 +331,19 @@ program VkCompute { file_write(f, row, W * 3) } file_close(f) - vk_unmap_memory(dev, mem) + vk_unmap_memory(vkcompute_st.dev, mem) print(`vulkan: {lit} of {W * H} pixels written by the shader -> {out_path}`) - vk_destroy_fence(dev, vk_get_i64(fences, 0), null) - vk_destroy_command_pool(dev, cmd_pool, null) - vk_destroy_descriptor_pool(dev, pool, null) - vk_destroy_pipeline(dev, pipeline, null) - vk_destroy_pipeline_layout(dev, layout, null) - vk_destroy_descriptor_set_layout(dev, vk_get_i64(set_layouts, 0), null) - vk_destroy_shader_module(dev, module, null) - vk_destroy_buffer(dev, buf, null) - vk_free_memory(dev, mem, null) - vk_destroy_device(dev, null) + vk_destroy_fence(vkcompute_st.dev, vk_get_i64(fences, 0), null) + vk_destroy_command_pool(vkcompute_st.dev, cmd_pool, null) + vk_destroy_descriptor_pool(vkcompute_st.dev, pool, null) + vk_destroy_pipeline(vkcompute_st.dev, pipeline, null) + vk_destroy_pipeline_layout(vkcompute_st.dev, layout, null) + vk_destroy_descriptor_set_layout(vkcompute_st.dev, vk_get_i64(set_layouts, 0), null) + vk_destroy_shader_module(vkcompute_st.dev, module, null) + vk_destroy_buffer(vkcompute_st.dev, buf, null) + vk_free_memory(vkcompute_st.dev, mem, null) + vk_destroy_device(vkcompute_st.dev, null) vk_destroy_instance(inst, null) if lit == W * H { print("VKCOMPUTE OK") } else { print("VKCOMPUTE INCOMPLETE") } quit() diff --git a/examples/rendering/vk_resources.ludic b/examples/rendering/vk_resources.ludic index 6efadf9c..78333547 100644 --- a/examples/rendering/vk_resources.ludic +++ b/examples/rendering/vk_resources.ludic @@ -13,12 +13,14 @@ program VkResources { property Marker { on: int = 1 } model Anchor { Marker } - var failures: int = 0 - unsafe function check(what: string, ok: bool) -> void { - if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); failures += 1 } + state VkresourcesState { + failures: int = 0 + } + unsafe function check(vkresources_st: mut VkresourcesState, what: string, ok: bool) -> void { + if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); vkresources_st.failures += 1 } } - handler Boot phase Start { + handler Boot(vkresources_st: mut VkresourcesState) phase Start { spawn Anchor {} print(`vulkan: OpenGL names spliced (pixel scale {Gl.pixel_scale()})`) if not gvk_init() { print(`VKRES NONE: {gvk_why}`); quit() } @@ -29,56 +31,56 @@ program VkResources { let px = bytes(w * h * 4) for i in 0 .. w * h { px[i * 4] = i & 255; px[i * 4 + 1] = (i >> 8) & 255; px[i * 4 + 2] = (i * 7) & 255; px[i * 4 + 3] = 200 } let t1 = gvk_tex_new() - check("RGBA8 storage with mips", gvk_tex_storage(t1, false, GL_RGBA8, w, h, 1, true)) - check("RGBA8 upload", gvk_tex_upload(t1, GL_RGBA8, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, px)) - check("RGBA8 mip chain (7 levels)", gvk_tex_levels[t1] == 7 and gvk_tex_mips(t1, w, h)) + check(vkresources_st, "RGBA8 storage with mips", gvk_tex_storage(t1, false, GL_RGBA8, w, h, 1, true)) + check(vkresources_st, "RGBA8 upload", gvk_tex_upload(t1, GL_RGBA8, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, px)) + check(vkresources_st, "RGBA8 mip chain (7 levels)", gvk_tex_levels[t1] == 7 and gvk_tex_mips(t1, w, h)) let back = bytes(w * h * 4) - check("RGBA8 read-back call", gvk_tex_read(t1, GL_RGBA8, w, h, GL_RGBA, GL_UNSIGNED_BYTE, back)) + check(vkresources_st, "RGBA8 read-back call", gvk_tex_read(t1, GL_RGBA8, w, h, GL_RGBA, GL_UNSIGNED_BYTE, back)) var same = true for i in 0 .. w * h * 4 { if back[i] != px[i] { same = false } } - check("RGBA8 read-back matches", same) + check(vkresources_st, "RGBA8 read-back matches", same) # R32F, the terrain's height field path let hf = bytes(w * h * 4) for i in 0 .. w * h { Vk.put_i32(hf, i * 4, 0x40000000 + i) } let t2 = gvk_tex_new() - check("R32F target", gvk_tex_storage(t2, false, GL_R32F, w, h, 1, false)) - check("R32F upload", gvk_tex_upload(t2, GL_R32F, w, h, 1, GL_RED, GL_FLOAT, hf)) + check(vkresources_st, "R32F target", gvk_tex_storage(t2, false, GL_R32F, w, h, 1, false)) + check(vkresources_st, "R32F upload", gvk_tex_upload(t2, GL_R32F, w, h, 1, GL_RED, GL_FLOAT, hf)) let hb = bytes(w * h * 4) - check("R32F read-back call", gvk_tex_read(t2, GL_R32F, w, h, GL_RED, GL_FLOAT, hb)) + check(vkresources_st, "R32F read-back call", gvk_tex_read(t2, GL_R32F, w, h, GL_RED, GL_FLOAT, hb)) var hsame = true for i in 0 .. w * h * 4 { if hb[i] != hf[i] { hsame = false } } - check("R32F read-back matches", hsame) + check(vkresources_st, "R32F read-back matches", hsame) # RGB8 stored as RGBA, and an HDR image's float RGB into half floats let rgb = bytes(w * h * 3) for i in 0 .. w * h * 3 { rgb[i] = (i * 3) & 255 } let t3 = gvk_tex_new() - check("SRGB8 expanded to RGBA", gvk_tex_storage(t3, false, GL_SRGB8, w, h, 1, true) and gvk_tex_upload(t3, GL_SRGB8, w, h, 1, GL_RGB, GL_UNSIGNED_BYTE, rgb)) + check(vkresources_st, "SRGB8 expanded to RGBA", gvk_tex_storage(t3, false, GL_SRGB8, w, h, 1, true) and gvk_tex_upload(t3, GL_SRGB8, w, h, 1, GL_RGB, GL_UNSIGNED_BYTE, rgb)) let fl = bytes(w * h * 12) for i in 0 .. w * h * 3 { Vk.put_i32(fl, i * 4, 0x3FC00000) } let t4 = gvk_tex_new() - check("RGB16F from floats (halved)", gvk_tex_storage(t4, false, GL_RGB16F, w, h, 1, true) and gvk_tex_upload(t4, GL_RGB16F, w, h, 1, GL_RGB, GL_FLOAT, fl)) - check("half of 1.5 is 0x3E00", gvk_half(0x3FC00000) == 0x3E00) + check(vkresources_st, "RGB16F from floats (halved)", gvk_tex_storage(t4, false, GL_RGB16F, w, h, 1, true) and gvk_tex_upload(t4, GL_RGB16F, w, h, 1, GL_RGB, GL_FLOAT, fl)) + check(vkresources_st, "half of 1.5 is 0x3E00", gvk_half(0x3FC00000) == 0x3E00) # a 16-bit RGB PNG (a normal map): big-endian samples, swapped on upload, alpha filled opaque let n16 = bytes(w * h * 6) for i in 0 .. w * h * 3 { n16[i * 2] = (i >> 8) & 255; n16[i * 2 + 1] = i & 255 } let t7 = gvk_tex_new() gvk_unpack_swap = true - check("RGB16 swapped upload", gvk_tex_storage(t7, false, GL_RGB16, w, h, 1, true) and gvk_tex_upload(t7, GL_RGB16, w, h, 1, GL_RGB, GL_UNSIGNED_SHORT, n16)) + check(vkresources_st, "RGB16 swapped upload", gvk_tex_storage(t7, false, GL_RGB16, w, h, 1, true) and gvk_tex_upload(t7, GL_RGB16, w, h, 1, GL_RGB, GL_UNSIGNED_SHORT, n16)) gvk_unpack_swap = false let b16 = bytes(w * h * 6) - check("RGB16 read-back call", gvk_tex_read(t7, GL_RGB16, w, h, GL_RGB, GL_UNSIGNED_SHORT, b16)) + check(vkresources_st, "RGB16 read-back call", gvk_tex_read(t7, GL_RGB16, w, h, GL_RGB, GL_UNSIGNED_SHORT, b16)) var s16 = true for i in 0 .. w * h * 3 { if b16[i * 2] != (i & 255) or b16[i * 2 + 1] != ((i >> 8) & 255) { s16 = false } } - check("RGB16 samples arrive little-endian", s16) + check(vkresources_st, "RGB16 samples arrive little-endian", s16) # the shadow cascades: a depth array, and a render-sized half-float target with no pixels let t5 = gvk_tex_new() - check("D32 array of 4", gvk_tex_storage(t5, true, GL_DEPTH_COMPONENT32F, 256, 256, 4, false)) + check(vkresources_st, "D32 array of 4", gvk_tex_storage(t5, true, GL_DEPTH_COMPONENT32F, 256, 256, 4, false)) let t6 = gvk_tex_new() - check("RGBA16F target", gvk_tex_storage(t6, false, GL_RGBA16F, 320, 180, 1, false)) + check(vkresources_st, "RGBA16F target", gvk_tex_storage(t6, false, GL_RGBA16F, 320, 180, 1, false)) # the samplers the renderer asks for, and that asking twice returns the same one let s1 = gvk_sampler(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000) @@ -86,25 +88,25 @@ program VkResources { let s3 = gvk_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER, GL_LEQUAL, 0) let s4 = gvk_sampler(GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0, 0) let again = gvk_sampler(GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT, 0, 0x41800000) - check("four samplers made", s1 != 0 and s2 != 0 and s3 != 0 and s4 != 0 and len(gvk_smp) == 4) - check("a sampler asked for twice is one sampler", again == s1) + check(vkresources_st, "four samplers made", s1 != 0 and s2 != 0 and s3 != 0 and s4 != 0 and len(gvk_smp) == 4) + check(vkresources_st, "a sampler asked for twice is one sampler", again == s1) # a buffer filled, filled again smaller (kept), then larger (grown) let b = gvk_buf_new() let data = bytes(4096) for i in 0 .. 4096 { data[i] = i & 255 } - check("buffer upload", gvk_buf_upload(b, 1024, data)) + check(vkresources_st, "buffer upload", gvk_buf_upload(b, 1024, data)) let first = gvk_buf[b] - check("a smaller upload keeps the buffer", gvk_buf_upload(b, 512, data) and gvk_buf[b] == first) - check("a larger upload grows it", gvk_buf_upload(b, 4096, data) and gvk_buf_size[b] >= 4096) + check(vkresources_st, "a smaller upload keeps the buffer", gvk_buf_upload(b, 512, data) and gvk_buf[b] == first) + check(vkresources_st, "a larger upload grows it", gvk_buf_upload(b, 4096, data) and gvk_buf_size[b] >= 4096) for t in 1 .. 8 { gvk_tex_release(t) } gvk_buf_release(b) for i in 0 .. len(gvk_smp) { Vk.destroy_sampler(gvk_dev, gvk_smp[i], null) } print(`vulkan: {gvk_n_allocs} allocations left after freeing`) - check("every allocation freed", gvk_n_allocs == 0) + check(vkresources_st, "every allocation freed", gvk_n_allocs == 0) gvk_shutdown() - if failures == 0 { print("VKRES OK") } else { print(`VKRES FAILED ({failures})`) } + if vkresources_st.failures == 0 { print("VKRES OK") } else { print(`VKRES FAILED ({vkresources_st.failures})`) } quit() } } diff --git a/examples/rendering/vk_triangle.ludic b/examples/rendering/vk_triangle.ludic index b642c6a0..2ddee991 100644 --- a/examples/rendering/vk_triangle.ludic +++ b/examples/rendering/vk_triangle.ludic @@ -29,8 +29,10 @@ program VkTriangle { } return false } - var spv_len: int = 0 - unsafe function read_all(path: string) -> bytes { + state VktriangleState { + spv_len: int = 0 + } + unsafe function read_all(vktriangle_st: mut VktriangleState, path: string) -> bytes { let f = file_open(path, "rb") if f == null { return null } file_seek(f, 0, 2) @@ -39,7 +41,7 @@ program VkTriangle { let b = bytes(n + 4) file_read(f, b, n) file_close(f) - spv_len = n + vktriangle_st.spv_len = n return b } # the first memory type the resource allows that has every property wanted @@ -72,13 +74,13 @@ program VkTriangle { color_range(b, VkImageMemoryBarrier_subresourceRange) vk_cmd_pipeline_barrier(cb, src_stage, dst_stage, 0, 0, null, 0, null, 1, b) } - unsafe function shader_module(dev: pointer, path: string) -> long { - let spv = read_all(path) + unsafe function shader_module(vktriangle_st: mut VktriangleState, dev: pointer, path: string) -> long { + let spv = read_all(vktriangle_st, path) if spv == null { print(`vulkan: no SPIR-V at {path} (compile vk_triangle.slang with slangc)`); quit() } let smci = bytes(VkShaderModuleCreateInfo_sizeof) vk_zero(smci, VkShaderModuleCreateInfo_sizeof) vk_put_i32(smci, VkShaderModuleCreateInfo_sType, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) - let code_size: long = spv_len + let code_size: long = vktriangle_st.spv_len vk_put_i64(smci, VkShaderModuleCreateInfo_codeSize, code_size) vk_put_ptr(smci, VkShaderModuleCreateInfo_pCode, spv) let out = bytes(8) @@ -87,7 +89,7 @@ program VkTriangle { return handle(out) } - handler Boot phase Start { + handler Boot(vktriangle_st: mut VktriangleState) phase Start { spawn Anchor {} var vs_path = "build/vk_triangle_vs.spv" var fs_path = "build/vk_triangle_fs.spv" @@ -253,8 +255,8 @@ program VkTriangle { if r != VK_SUCCESS { fail("vkBindBufferMemory", r) } # ---- the pipeline: two stages, no vertex input, dynamic viewport and scissor, one colour format - let vs = shader_module(dev, vs_path) - let fs = shader_module(dev, fs_path) + let vs = shader_module(vktriangle_st, dev, vs_path) + let fs = shader_module(vktriangle_st, dev, fs_path) let plci = bytes(VkPipelineLayoutCreateInfo_sizeof) vk_zero(plci, VkPipelineLayoutCreateInfo_sizeof) vk_put_i32(plci, VkPipelineLayoutCreateInfo_sType, VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO) diff --git a/packages/ludic.dungeon/dungeon.ludic b/packages/ludic.dungeon/dungeon.ludic index a7400098..30c1164e 100644 --- a/packages/ludic.dungeon/dungeon.ludic +++ b/packages/ludic.dungeon/dungeon.ludic @@ -20,8 +20,10 @@ enum RoomStyle { Pillars, Columns, CenterSlab, Bars, Scatter } const DUNGEON_MARGIN: int = 2 -var dungeon_wall: int = '#' -var dungeon_floor: int = '.' +state DungeonState { + dungeon_wall: int = '#' + dungeon_floor: int = '.' +} function dungeon_columns() -> int { return Map.width() } function dungeon_rows() -> int { return Map.height() } @@ -29,62 +31,62 @@ function dungeon_door_column() -> int { return Map.width() / 2 - 1 } # the l function dungeon_door_row() -> int { return Map.height() / 2 } # the top of the two door cells # a wall, but never in the margin so rooms stay traversable -function dungeon_place(x: int, y: int) -> void { +function dungeon_place(dungeon_st: DungeonState, x: int, y: int) -> void { if (x < DUNGEON_MARGIN) or (x > dungeon_columns() - 1 - DUNGEON_MARGIN) { return } if (y < DUNGEON_MARGIN) or (y > dungeon_rows() - 1 - DUNGEON_MARGIN) { return } - Map.set(x: x, y: y, glyph: dungeon_wall) + Map.set(x: x, y: y, glyph: dungeon_st.dungeon_wall) } -function dungeon_mirrored(x: int, y: int) -> void { - dungeon_place(x, y) - dungeon_place(dungeon_columns() - 1 - x, y) - dungeon_place(x, dungeon_rows() - 1 - y) - dungeon_place(dungeon_columns() - 1 - x, dungeon_rows() - 1 - y) +function dungeon_mirrored(dungeon_st: DungeonState, x: int, y: int) -> void { + dungeon_place(dungeon_st, x, y) + dungeon_place(dungeon_st, dungeon_columns() - 1 - x, y) + dungeon_place(dungeon_st, x, dungeon_rows() - 1 - y) + dungeon_place(dungeon_st, dungeon_columns() - 1 - x, dungeon_rows() - 1 - y) } -function dungeon_pillar(x: int, y: int) -> void { # 2x2, mirrored four ways - dungeon_mirrored(x, y) - dungeon_mirrored(x + 1, y) - dungeon_mirrored(x, y + 1) - dungeon_mirrored(x + 1, y + 1) +function dungeon_pillar(dungeon_st: DungeonState, x: int, y: int) -> void { # 2x2, mirrored four ways + dungeon_mirrored(dungeon_st, x, y) + dungeon_mirrored(dungeon_st, x + 1, y) + dungeon_mirrored(dungeon_st, x, y + 1) + dungeon_mirrored(dungeon_st, x + 1, y + 1) } -function dungeon_style(style: int) -> void { +function dungeon_style(dungeon_st: DungeonState, style: int) -> void { let cols = dungeon_columns() let rows = dungeon_rows() match style { RoomStyle.Pillars => { let x = Random.range(low: 3, high: 5) let y = Random.range(low: 3, high: 4) - dungeon_pillar(x, y) - if Random.chance(percent: 60) { dungeon_pillar(x + 3, y + 2) } + dungeon_pillar(dungeon_st, x, y) + if Random.chance(percent: 60) { dungeon_pillar(dungeon_st, x + 3, y + 2) } } RoomStyle.Columns => { let x = Random.range(low: 4, high: 6) - for y in 3 .. rows - 3 { dungeon_mirrored(x, y) } + for y in 3 .. rows - 3 { dungeon_mirrored(dungeon_st, x, y) } } - RoomStyle.CenterSlab => { Map.rect(x: 6, y: 5, width: cols - 12, height: rows - 10, glyph: dungeon_wall) } + RoomStyle.CenterSlab => { Map.rect(x: 6, y: 5, width: cols - 12, height: rows - 10, glyph: dungeon_st.dungeon_wall) } RoomStyle.Bars => { let y = Random.range(low: 3, high: 4) - for x in 4 .. cols - 4 { dungeon_mirrored(x, y) } + for x in 4 .. cols - 4 { dungeon_mirrored(dungeon_st, x, y) } } _ => { for i in 0 .. Random.range(low: 3, high: 5) { let x = Random.range(low: 3, high: cols / 2 - 1) - dungeon_place(x, Random.range(low: 3, high: rows - 4)) - dungeon_place(cols - 1 - x, Random.range(low: 3, high: rows - 4)) + dungeon_place(dungeon_st, x, Random.range(low: 3, high: rows - 4)) + dungeon_place(dungeon_st, cols - 1 - x, Random.range(low: 3, high: rows - 4)) } } } } -@Namespace(Dungeon) function dungeon_open_arena(wall: int, floor: int) -> void { - dungeon_wall = wall - dungeon_floor = floor +@Namespace(Dungeon) function dungeon_open_arena(dungeon_st: mut DungeonState, wall: int, floor: int) -> void { + dungeon_st.dungeon_wall = wall + dungeon_st.dungeon_floor = floor Map.fill(glyph: floor) Map.border(glyph: wall) } -@Namespace(Dungeon) function dungeon_arena(style: int, wall: int, floor: int) -> void { - dungeon_open_arena(wall, floor) - dungeon_style(style) +@Namespace(Dungeon) function dungeon_arena(dungeon_st: mut DungeonState, style: int, wall: int, floor: int) -> void { + dungeon_open_arena(dungeon_st, wall, floor) + dungeon_style(dungeon_st, style) Map.rect(x: dungeon_door_column(), y: 1, width: 2, height: dungeon_rows() - 2, glyph: floor) # the door lanes Map.rect(x: 1, y: dungeon_door_row(), width: dungeon_columns() - 2, height: 2, glyph: floor) } diff --git a/packages/ludic.gameplay/combat.ludic b/packages/ludic.gameplay/combat.ludic index bb27d7ea..f2e1c283 100644 --- a/packages/ludic.gameplay/combat.ludic +++ b/packages/ludic.gameplay/combat.ludic @@ -23,13 +23,16 @@ event Died { e: int = 0, src: int = 0 } event Healed { e: int = 0, amount: int = 0 } event Crit { target: int = 0, src: int = 0 } # the attacker's crit_pct fired -var combat_reflecting: bool = false # thorns never reflect thorns +state GameplayState { + combat_reflecting: bool = false # thorns never reflect thorns + combat_pending: int = 0 + fac_tbl: words = null +} # the mutable channel for lever 6: listeners rewrite the pending damage here. -var combat_pending: int = 0 -@Namespace(Combat) function combat_amount() -> int { return combat_pending } -@Namespace(Combat) function combat_set_amount(n: int) -> void { combat_pending = n } +@Namespace(Combat) function combat_amount(gameplay_st: GameplayState) -> int { return gameplay_st.combat_pending } +@Namespace(Combat) function combat_set_amount(gameplay_st: mut GameplayState, n: int) -> void { gameplay_st.combat_pending = n } # Combat.mitigate(target, raw) — raw minus effective defence, floored at 1 so any # landed hit chips at least a point (a common, tunable default). A game that wants @@ -43,7 +46,7 @@ var combat_pending: int = 0 # Combat.damage(target, src, amount) — the full pipeline. Returns the damage # actually applied (0 when vetoed or the target has no Stats/hp). -@Namespace(Combat) function combat_damage(target: int, src: int, amount: int) -> int { +@Namespace(Combat) function combat_damage(gameplay_st: mut GameplayState, target: int, src: int, amount: int) -> int { let P = World.prop_id("Stats") if P < 0 { return 0 } if World.has(target, P) == 0 { return 0 } @@ -54,9 +57,9 @@ var combat_pending: int = 0 var raw = amount * stats_field(src, "damage_pct", 100) / 100 let crit = stats_field(src, "crit_pct", 0) if (crit > 0) and Random.chance(percent: crit) { raw *= 2; emit Crit(target: target, src: src) } - combat_pending = combat_mitigate(target, raw) - if emit DamageAboutToApply(target: target, src: src, amount: combat_pending) != 0 { return 0 } - var dmg = combat_pending + gameplay_st.combat_pending = combat_mitigate(target, raw) + if emit DamageAboutToApply(target: target, src: src, amount: gameplay_st.combat_pending) != 0 { return 0 } + var dmg = gameplay_st.combat_pending if dmg < 0 { dmg = 0 } var hp = World.get(target, P, fhp) - dmg @@ -69,10 +72,10 @@ var combat_pending: int = 0 let leech = stats_field(src, "leech_pct", 0) if (leech > 0) and Random.chance(percent: leech) { combat_heal(src, stats_field(src, "leech_hp", 1)) } let thorns = stats_field(target, "thorns", 0) - if (thorns > 0) and (src != target) and (not combat_reflecting) and (World.has(src, P) != 0) { - combat_reflecting = true - combat_damage(src, target, thorns) - combat_reflecting = false + if (thorns > 0) and (src != target) and (not gameplay_st.combat_reflecting) and (World.has(src, P) != 0) { + gameplay_st.combat_reflecting = true + combat_damage(gameplay_st, src, target, thorns) + gameplay_st.combat_reflecting = false } } return dmg diff --git a/packages/ludic.gameplay/faction.ludic b/packages/ludic.gameplay/faction.ludic index 7078ef92..95d0301f 100644 --- a/packages/ludic.gameplay/faction.ludic +++ b/packages/ludic.gameplay/faction.ludic @@ -16,26 +16,25 @@ const REL_HOSTILE: int = 2 # relationships stored as (rel + 1); a zero slot means "unset -> use the default". # A global var initialiser must be a literal, so it is allocated lazily. -var fac_tbl: words = null -function fac_ensure() -> void { - if fac_tbl == null { fac_tbl = words(FAC_MAX * FAC_MAX) } +function fac_ensure(gameplay_st: mut GameplayState) -> void { + if gameplay_st.fac_tbl == null { gameplay_st.fac_tbl = words(FAC_MAX * FAC_MAX) } } # Faction.set(a, b, rel) — record a symmetric relationship between two factions. -@Namespace(Faction) function faction_set(a: int, b: int, rel: int) -> void { - fac_ensure() +@Namespace(Faction) function faction_set(gameplay_st: mut GameplayState, a: int, b: int, rel: int) -> void { + fac_ensure(gameplay_st) if (a < 0) or (a >= FAC_MAX) { return } if (b < 0) or (b >= FAC_MAX) { return } - fac_tbl[a * FAC_MAX + b] = rel + 1 - fac_tbl[b * FAC_MAX + a] = rel + 1 + gameplay_st.fac_tbl[a * FAC_MAX + b] = rel + 1 + gameplay_st.fac_tbl[b * FAC_MAX + a] = rel + 1 } # Faction.rel(a, b) — the relationship code (0 friend, 1 neutral, 2 hostile). -@Namespace(Faction) function faction_rel(a: int, b: int) -> int { - fac_ensure() +@Namespace(Faction) function faction_rel(gameplay_st: mut GameplayState, a: int, b: int) -> int { + fac_ensure(gameplay_st) if (a >= 0) and (a < FAC_MAX) and (b >= 0) and (b < FAC_MAX) { - let v = fac_tbl[a * FAC_MAX + b] + let v = gameplay_st.fac_tbl[a * FAC_MAX + b] if v != 0 { return v - 1 } } if a == b { return REL_FRIEND } @@ -43,12 +42,12 @@ function fac_ensure() -> void { } # Faction.hostile(a, b) / Faction.friendly(a, b) — the common yes/no queries. -@Namespace(Faction) function faction_hostile(a: int, b: int) -> int { - if faction_rel(a, b) == REL_HOSTILE { return 1 } +@Namespace(Faction) function faction_hostile(gameplay_st: mut GameplayState, a: int, b: int) -> int { + if faction_rel(gameplay_st, a, b) == REL_HOSTILE { return 1 } return 0 } -@Namespace(Faction) function faction_friendly(a: int, b: int) -> int { - if faction_rel(a, b) == REL_FRIEND { return 1 } +@Namespace(Faction) function faction_friendly(gameplay_st: mut GameplayState, a: int, b: int) -> int { + if faction_rel(gameplay_st, a, b) == REL_FRIEND { return 1 } return 0 } @@ -65,6 +64,6 @@ function fac_ensure() -> void { # Faction.enemies(a, b) — true when the two entities' factions are hostile; the # one-call test the shooter and AI reach for. Missing factions read as hostile # (an unaligned hazard hits anyone) unless the ids happen to match. -@Namespace(Faction) function faction_enemies(ea: int, eb: int) -> int { - return faction_hostile(faction_id_of(ea), faction_id_of(eb)) +@Namespace(Faction) function faction_enemies(gameplay_st: mut GameplayState, ea: int, eb: int) -> int { + return faction_hostile(gameplay_st, faction_id_of(ea), faction_id_of(eb)) } diff --git a/packages/ludic.lab/example/plate.ludic b/packages/ludic.lab/example/plate.ludic index 7c406e59..da90f718 100644 --- a/packages/ludic.lab/example/plate.ludic +++ b/packages/ludic.lab/example/plate.ludic @@ -6,15 +6,15 @@ import "ludic.lab" program LabPlate { numbers float - function probe_setup() -> void { - lab_model(lab_plate_dir(), "probe.gltf", "probe", 0.0, 0.0, 0.5) - lab_note("a box and a ball, in the plate's light") - lab_shot_at("front", 0.0, 0.7, 0.0, 4.0, 0.0, 12.0) - lab_shot_at("side", 0.0, 0.7, 0.0, 4.0, 90.0, 12.0) - lab_shot_at("above", 0.0, 0.5, 0.0, 7.0, 30.0, 55.0) + function probe_setup(lab_st: mut LabState, render3d_st: mut Render3dState) -> void { + lab_model(render3d_st, lab_plate_dir(lab_st), "probe.gltf", "probe", 0.0, 0.0, 0.5) + lab_note(lab_st, "a box and a ball, in the plate's light") + lab_shot_at(lab_st, "front", 0.0, 0.7, 0.0, 4.0, 0.0, 12.0) + lab_shot_at(lab_st, "side", 0.0, 0.7, 0.0, 4.0, 90.0, 12.0) + lab_shot_at(lab_st, "above", 0.0, 0.5, 0.0, 7.0, 30.0, 55.0) } def LabScenes probe { setup: fn probe_setup } - handler Boot phase Start { lab_boot() } - handler Look phase Input { lab_input() } - handler Draw phase Render { lab_render() } + handler Boot(lab_st: mut LabState, render3d_st: mut Render3dState) phase Start { lab_boot(lab_st, render3d_st) } + handler Look(lab_st: mut LabState, render3d_st: mut Render3dState) phase Input { lab_input(lab_st, render3d_st) } + handler Draw(lab_st: mut LabState, render3d_st: mut Render3dState) phase Render { lab_render(lab_st, render3d_st) } } diff --git a/packages/ludic.net/peers.ludic b/packages/ludic.net/peers.ludic index 86ccc9c7..96d61833 100644 --- a/packages/ludic.net/peers.ludic +++ b/packages/ludic.net/peers.ludic @@ -17,7 +17,7 @@ export function np_of_pid(net_st: NetState, pid: int) -> int { return -1 } -function net__open(net_st: NetState, slot: int, ip: int, port: int, pid: int) -> void { +function net__open(net_st: mut NetState, slot: int, ip: int, port: int, pid: int) -> void { let p = net_st.net__peers[slot] p.on = true p.ip = ip @@ -32,7 +32,7 @@ function net__open(net_st: NetState, slot: int, ip: int, port: int, pid: int) -> p.closing = false } -export function np_close(net_st: NetState, slot: int) -> void { +export function np_close(net_st: mut NetState, slot: int) -> void { if net_st.net__peers == null or slot < 0 or slot >= NET_PEERS { return } let p = net_st.net__peers[slot] p.on = false @@ -41,7 +41,7 @@ export function np_close(net_st: NetState, slot: int) -> void { } # the written message, for one peer (a reliable one is kept until it is taken) -export function np_queue(net_st: NetState, slot: int, kind: int, rel: bool) -> void { +export function np_queue(net_st: mut NetState, slot: int, kind: int, rel: bool) -> void { if net_st.net__peers == null or slot < 0 or slot >= NET_PEERS { return } let p = net_st.net__peers[slot] if not p.on { return } @@ -63,7 +63,7 @@ export function np_queue(net_st: NetState, slot: int, kind: int, rel: bool) -> v } # to everyone this machine talks to (a host: every guest but `except`; a guest: the host) -export function np_queue_all(net_st: NetState, kind: int, rel: bool, except: int) -> void { +export function np_queue_all(net_st: mut NetState, kind: int, rel: bool, except: int) -> void { if net_st.net__peers == null { return } for i in 0 .. NET_PEERS { if net_st.net__peers[i].on and i != except { np_queue(net_st, i, kind, rel) } } } @@ -94,7 +94,7 @@ export function net_peer_heard(net_st: NetState, slot: int) -> float { } # let the slot go once it has taken everything queued for it so far, or after `secs` -export function net_close_when_sent(net_st: NetState, slot: int, secs: float) -> void { +export function net_close_when_sent(net_st: mut NetState, slot: int, secs: float) -> void { if not net_peer_on(net_st, slot) { return } let p = net_st.net__peers[slot] p.closing = true diff --git a/packages/ludic.net/receive.ludic b/packages/ludic.net/receive.ludic index 035c33a8..d1e5b104 100644 --- a/packages/ludic.net/receive.ludic +++ b/packages/ludic.net/receive.ludic @@ -29,7 +29,7 @@ function net__packet(base_st: mut BaseState, net_st: mut NetState, n: int, ip: i } # the known peer at that address, a stranger's new slot (a host, and only with a hello), or -1 -function net__slot_for(net_st: NetState, n: int, ip: int, port: int) -> int { +function net__slot_for(net_st: mut NetState, n: int, ip: int, port: int) -> int { let token = nb_get32(net_st.net__in, 4) var slot = net__find(net_st, ip, port) if slot >= 0 { diff --git a/packages/ludic.prefs/prefs.ludic b/packages/ludic.prefs/prefs.ludic index 099e0a3a..6b1ddcf9 100644 --- a/packages/ludic.prefs/prefs.ludic +++ b/packages/ludic.prefs/prefs.ludic @@ -9,49 +9,51 @@ const PREFS_MAX: int = 64 -var prefs_path: pointer = null -var prefs_names: pointers = null -var prefs_vals: words = null -var prefs_n: int = 0 +state PrefsState { + prefs_path: pointer = null + prefs_names: pointers = null + prefs_vals: words = null + prefs_n: int = 0 +} namespace Prefs { - internal function init() -> void { - if prefs_names != null { return } - prefs_names = pointers(PREFS_MAX) - prefs_vals = words(PREFS_MAX) - prefs_n = 0 + internal function init(prefs_st: mut PrefsState) -> void { + if prefs_st.prefs_names != null { return } + prefs_st.prefs_names = pointers(PREFS_MAX) + prefs_st.prefs_vals = words(PREFS_MAX) + prefs_st.prefs_n = 0 } - internal function find(key: pointer) -> int { + internal function find(prefs_st: PrefsState, key: pointer) -> int { var i = 0 - while i < prefs_n { if prefs_names[i] == key { return i }; i += 1 } + while i < prefs_st.prefs_n { if prefs_st.prefs_names[i] == key { return i }; i += 1 } return -1 } - export function get(key: pointer, fallback: int) -> int { - init() - let i = find(key) + export function get(prefs_st: mut PrefsState, key: pointer, fallback: int) -> int { + init(prefs_st) + let i = find(prefs_st, key) if i < 0 { return fallback } - return prefs_vals[i] + return prefs_st.prefs_vals[i] } # keep the larger of the stored value and `value`; true when `value` is a new record - export function max(key: pointer, value: int) -> bool { - if value <= get(key, 0) { return false } - set(key, value) + export function max(prefs_st: mut PrefsState, key: pointer, value: int) -> bool { + if value <= get(prefs_st, key, 0) { return false } + set(prefs_st, key, value) return true } - export function set(key: pointer, value: int) -> void { - init() - var i = find(key) + export function set(prefs_st: mut PrefsState, key: pointer, value: int) -> void { + init(prefs_st) + var i = find(prefs_st, key) if i < 0 { - if prefs_n >= PREFS_MAX { return } - i = prefs_n - prefs_names[i] = key - prefs_n += 1 + if prefs_st.prefs_n >= PREFS_MAX { return } + i = prefs_st.prefs_n + prefs_st.prefs_names[i] = key + prefs_st.prefs_n += 1 } - prefs_vals[i] = value + prefs_st.prefs_vals[i] = value } # the text of a whole file, or null when it does not exist @@ -69,7 +71,7 @@ namespace Prefs { } # digits after `=` on one line -> an int (a leading '-' negates) - internal function parse_line(buf: pointer, from: int, to: int) -> void { + internal function parse_line(prefs_st: mut PrefsState, buf: pointer, from: int, to: int) -> void { var eq = from while (eq < to) and (buf[eq] != '=') { eq += 1 } # '=' if eq >= to { return } @@ -83,21 +85,21 @@ namespace Prefs { if (p < to) and (buf[p] == '-') { neg = true; p += 1 } # '-' while (p < to) and (buf[p] >= '0') and (buf[p] <= '9') { v = v * 10 + (buf[p] - 48); p += 1 } if neg { v = -v } - set(key, v) + set(prefs_st, key, v) } - export function load(path: pointer) -> void { - init() - prefs_path = path + export function load(prefs_st: mut PrefsState, path: pointer) -> void { + init(prefs_st) + prefs_st.prefs_path = path let buf = read_all(path) if buf == null { return } var start = 0 var i = 0 while buf[i] != 0 { - if buf[i] == '\n' { parse_line(buf, start, i); start = i + 1 } # newline + if buf[i] == '\n' { parse_line(prefs_st, buf, start, i); start = i + 1 } # newline i += 1 } - if i > start { parse_line(buf, start, i) } + if i > start { parse_line(prefs_st, buf, start, i) } } internal function write_text(f: pointer, s: pointer) -> void { @@ -106,14 +108,14 @@ namespace Prefs { file_write(f, s, n) } - export function save() -> void { - init() - if prefs_path == null { return } - let f = file_open(prefs_path, "wb") + export function save(prefs_st: mut PrefsState) -> void { + init(prefs_st) + if prefs_st.prefs_path == null { return } + let f = file_open(prefs_st.prefs_path, "wb") if f == null { return } var i = 0 - while i < prefs_n { - write_text(f, `{prefs_names[i]}={string(prefs_vals[i])}\n`) + while i < prefs_st.prefs_n { + write_text(f, `{prefs_st.prefs_names[i]}={string(prefs_st.prefs_vals[i])}\n`) i += 1 } file_close(f) diff --git a/packages/ludic.rpg/crafting.ludic b/packages/ludic.rpg/crafting.ludic index 7f058a87..d54c2861 100644 --- a/packages/ludic.rpg/crafting.ludic +++ b/packages/ludic.rpg/crafting.ludic @@ -9,20 +9,12 @@ import "ludic.rpg/inventory.ludic" const CRAFT_MAX: int = 128 const CRAFT_ING_MAX: int = 512 -var recipe_reg: Dict = null # recipe name -> id -var recipe_n: int = 0 -var recipe_out: pointers = null # output item name -var recipe_outqty: words = null -var ing_n: int = 0 -var ing_rid: words = null # owning recipe id -var ing_name: pointers = null # ingredient item name -var ing_qty: words = null -function craft_ensure() -> void { - if recipe_reg == null { - recipe_reg = Dict.new() - recipe_out = pointers(CRAFT_MAX); recipe_outqty = words(CRAFT_MAX) - ing_rid = words(CRAFT_ING_MAX); ing_name = pointers(CRAFT_ING_MAX); ing_qty = words(CRAFT_ING_MAX) +function craft_ensure(rpg_st: mut RpgState) -> void { + if rpg_st.recipe_reg == null { + rpg_st.recipe_reg = Dict.new() + rpg_st.recipe_out = pointers(CRAFT_MAX); rpg_st.recipe_outqty = words(CRAFT_MAX) + rpg_st.ing_rid = words(CRAFT_ING_MAX); rpg_st.ing_name = pointers(CRAFT_ING_MAX); rpg_st.ing_qty = words(CRAFT_ING_MAX) } } @@ -30,35 +22,35 @@ event cancellable CraftRequested { e: int = 0, recipe: int = 0 } event CraftCompleted { e: int = 0, recipe: int = 0 } # Craft.recipe(name, out_item, out_qty) — declare a recipe; returns its id. -@Namespace(Craft) function craft_recipe(name: pointer, out_item: pointer, out_qty: int) -> int { - craft_ensure() - if recipe_n >= CRAFT_MAX { return -1 } - let id = recipe_n - recipe_out[id] = out_item; recipe_outqty[id] = out_qty - recipe_n += 1 - Dict.set(recipe_reg, name, id) +@Namespace(Craft) function craft_recipe(rpg_st: mut RpgState, name: pointer, out_item: pointer, out_qty: int) -> int { + craft_ensure(rpg_st) + if rpg_st.recipe_n >= CRAFT_MAX { return -1 } + let id = rpg_st.recipe_n + rpg_st.recipe_out[id] = out_item; rpg_st.recipe_outqty[id] = out_qty + rpg_st.recipe_n += 1 + Dict.set(rpg_st.recipe_reg, name, id) return id } -@Namespace(Craft) function craft_id(name: pointer) -> int { craft_ensure(); return Dict.get_or(recipe_reg, name, -1) } +@Namespace(Craft) function craft_id(rpg_st: mut RpgState, name: pointer) -> int { craft_ensure(rpg_st); return Dict.get_or(rpg_st.recipe_reg, name, -1) } # Craft.ingredient(recipe_name, item, qty) — add an ingredient requirement. -@Namespace(Craft) function craft_ingredient(recipe_name: pointer, item: pointer, qty: int) -> void { - craft_ensure() - let rid = craft_id(recipe_name) +@Namespace(Craft) function craft_ingredient(rpg_st: mut RpgState, recipe_name: pointer, item: pointer, qty: int) -> void { + craft_ensure(rpg_st) + let rid = craft_id(rpg_st, recipe_name) if rid < 0 { return } - if ing_n >= CRAFT_ING_MAX { return } - ing_rid[ing_n] = rid; ing_name[ing_n] = item; ing_qty[ing_n] = qty - ing_n += 1 + if rpg_st.ing_n >= CRAFT_ING_MAX { return } + rpg_st.ing_rid[rpg_st.ing_n] = rid; rpg_st.ing_name[rpg_st.ing_n] = item; rpg_st.ing_qty[rpg_st.ing_n] = qty + rpg_st.ing_n += 1 } # Craft.can(e, recipe_name) — does the owner hold every ingredient? -@Namespace(Craft) function craft_can(e: int, recipe_name: pointer) -> int { - craft_ensure() - let rid = craft_id(recipe_name) +@Namespace(Craft) function craft_can(rpg_st: mut RpgState, e: int, recipe_name: pointer) -> int { + craft_ensure(rpg_st) + let rid = craft_id(rpg_st, recipe_name) if rid < 0 { return 0 } var i = 0 - while i < ing_n { - if ing_rid[i] == rid { - if Inventory.count(e, ing_name[i]) < ing_qty[i] { return 0 } + while i < rpg_st.ing_n { + if rpg_st.ing_rid[i] == rid { + if Inventory.count(e, rpg_st.ing_name[i]) < rpg_st.ing_qty[i] { return 0 } } i += 1 } @@ -66,18 +58,18 @@ event CraftCompleted { e: int = 0, recipe: int = 0 } } # Craft.make(e, recipe_name) — consume ingredients + produce output; 1 on success. -@Namespace(Craft) function craft_make(e: int, recipe_name: pointer) -> int { - craft_ensure() - let rid = craft_id(recipe_name) +@Namespace(Craft) function craft_make(rpg_st: mut RpgState, e: int, recipe_name: pointer) -> int { + craft_ensure(rpg_st) + let rid = craft_id(rpg_st, recipe_name) if rid < 0 { return 0 } - if craft_can(e, recipe_name) == 0 { return 0 } + if craft_can(rpg_st, e, recipe_name) == 0 { return 0 } if emit CraftRequested(e: e, recipe: rid) != 0 { return 0 } var i = 0 - while i < ing_n { - if ing_rid[i] == rid { Inventory.remove(e, ing_name[i], ing_qty[i]) } + while i < rpg_st.ing_n { + if rpg_st.ing_rid[i] == rid { Inventory.remove(e, rpg_st.ing_name[i], rpg_st.ing_qty[i]) } i += 1 } - Inventory.add(e, recipe_out[rid], recipe_outqty[rid]) + Inventory.add(e, rpg_st.recipe_out[rid], rpg_st.recipe_outqty[rid]) emit CraftCompleted(e: e, recipe: rid) return 1 } diff --git a/packages/ludic.rpg/dialog.ludic b/packages/ludic.rpg/dialog.ludic index 41103b8d..91bd8e41 100644 --- a/packages/ludic.rpg/dialog.ludic +++ b/packages/ludic.rpg/dialog.ludic @@ -8,23 +8,13 @@ const DLG_TREE_MAX: int = 64 const DLG_NODE_MAX: int = 512 const DLG_CHOICE_MAX: int = 1024 -var dlg_reg: Dict = null # tree name -> tree id -var dlg_tree_n: int = 0 -var dlg_tree_start: words = null # first node id per tree -var dlg_node_n: int = 0 -var dlg_node_tree: words = null -var dlg_node_text: pointers = null -var dlg_choice_n: int = 0 -var dlg_choice_node: words = null # owning node -var dlg_choice_label: pointers = null -var dlg_choice_next: words = null # next node id, or -1 to end -function dlg_ensure() -> void { - if dlg_reg == null { - dlg_reg = Dict.new() - dlg_tree_start = words(DLG_TREE_MAX) - dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = pointers(DLG_NODE_MAX) - dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = pointers(DLG_CHOICE_MAX); dlg_choice_next = words(DLG_CHOICE_MAX) +function dlg_ensure(rpg_st: mut RpgState) -> void { + if rpg_st.dlg_reg == null { + rpg_st.dlg_reg = Dict.new() + rpg_st.dlg_tree_start = words(DLG_TREE_MAX) + rpg_st.dlg_node_tree = words(DLG_NODE_MAX); rpg_st.dlg_node_text = pointers(DLG_NODE_MAX) + rpg_st.dlg_choice_node = words(DLG_CHOICE_MAX); rpg_st.dlg_choice_label = pointers(DLG_CHOICE_MAX); rpg_st.dlg_choice_next = words(DLG_CHOICE_MAX) } } @@ -36,37 +26,37 @@ event cancellable DialogChoice { e: int = 0, node: int = 0, choice: int = 0 } event DialogEnded { e: int = 0 } # Dialog.tree(name) — register (or fetch) a tree id. -@Namespace(Dialog) function dialog_tree(name: pointer) -> int { - dlg_ensure() - let ex = Dict.get_or(dlg_reg, name, -1) +@Namespace(Dialog) function dialog_tree(rpg_st: mut RpgState, name: pointer) -> int { + dlg_ensure(rpg_st) + let ex = Dict.get_or(rpg_st.dlg_reg, name, -1) if ex >= 0 { return ex } - if dlg_tree_n >= DLG_TREE_MAX { return -1 } - let id = dlg_tree_n - dlg_tree_start[id] = -1 - dlg_tree_n += 1 - Dict.set(dlg_reg, name, id) + if rpg_st.dlg_tree_n >= DLG_TREE_MAX { return -1 } + let id = rpg_st.dlg_tree_n + rpg_st.dlg_tree_start[id] = -1 + rpg_st.dlg_tree_n += 1 + Dict.set(rpg_st.dlg_reg, name, id) return id } # Dialog.node(tree_name, text) — add a line node; the first node added to a tree is # its entry point. Returns the node id. -@Namespace(Dialog) function dialog_node(tree_name: pointer, text: pointer) -> int { - dlg_ensure() - let tid = dialog_tree(tree_name) +@Namespace(Dialog) function dialog_node(rpg_st: mut RpgState, tree_name: pointer, text: pointer) -> int { + dlg_ensure(rpg_st) + let tid = dialog_tree(rpg_st, tree_name) if tid < 0 { return -1 } - if dlg_node_n >= DLG_NODE_MAX { return -1 } - let id = dlg_node_n - dlg_node_tree[id] = tid; dlg_node_text[id] = text - dlg_node_n += 1 - if dlg_tree_start[tid] < 0 { dlg_tree_start[tid] = id } + if rpg_st.dlg_node_n >= DLG_NODE_MAX { return -1 } + let id = rpg_st.dlg_node_n + rpg_st.dlg_node_tree[id] = tid; rpg_st.dlg_node_text[id] = text + rpg_st.dlg_node_n += 1 + if rpg_st.dlg_tree_start[tid] < 0 { rpg_st.dlg_tree_start[tid] = id } return id } # Dialog.choice(node, label, next_node) — add a branch (next_node -1 ends). -@Namespace(Dialog) function dialog_choice(node: int, label: pointer, next_node: int) -> int { - dlg_ensure() - if dlg_choice_n >= DLG_CHOICE_MAX { return -1 } - let id = dlg_choice_n - dlg_choice_node[id] = node; dlg_choice_label[id] = label; dlg_choice_next[id] = next_node - dlg_choice_n += 1 +@Namespace(Dialog) function dialog_choice(rpg_st: mut RpgState, node: int, label: pointer, next_node: int) -> int { + dlg_ensure(rpg_st) + if rpg_st.dlg_choice_n >= DLG_CHOICE_MAX { return -1 } + let id = rpg_st.dlg_choice_n + rpg_st.dlg_choice_node[id] = node; rpg_st.dlg_choice_label[id] = label; rpg_st.dlg_choice_next[id] = next_node + rpg_st.dlg_choice_n += 1 return id } @@ -80,45 +70,45 @@ function dlg_set(e: int, name: pointer, v: int) -> void { } # Dialog.start(e, tree_name) — open the tree on a speaker (DialogStarted + Line). -@Namespace(Dialog) function dialog_start(e: int, tree_name: pointer) -> void { - dlg_ensure() - let tid = dialog_tree(tree_name) +@Namespace(Dialog) function dialog_start(rpg_st: mut RpgState, e: int, tree_name: pointer) -> void { + dlg_ensure(rpg_st) + let tid = dialog_tree(rpg_st, tree_name) if tid < 0 { return } dlg_set(e, "tree", tid) - dlg_set(e, "node", dlg_tree_start[tid]) + dlg_set(e, "node", rpg_st.dlg_tree_start[tid]) dlg_set(e, "active", 1) emit DialogStarted(e: e, tree: tid) - emit DialogLine(e: e, node: dlg_tree_start[tid]) + emit DialogLine(e: e, node: rpg_st.dlg_tree_start[tid]) } # Dialog.line(e) — text of the current node (empty when inactive). -@Namespace(Dialog) function dialog_line(e: int) -> pointer { +@Namespace(Dialog) function dialog_line(rpg_st: RpgState, e: int) -> pointer { if dlg_get(e, "active") == 0 { return "" } - return dlg_node_text[dlg_get(e, "node")] + return rpg_st.dlg_node_text[dlg_get(e, "node")] } # Dialog.count(e) — number of choices at the current node. -@Namespace(Dialog) function dialog_count(e: int) -> int { - dlg_ensure() +@Namespace(Dialog) function dialog_count(rpg_st: mut RpgState, e: int) -> int { + dlg_ensure(rpg_st) if dlg_get(e, "active") == 0 { return 0 } let node = dlg_get(e, "node") var n = 0; var i = 0 - while i < dlg_choice_n { if dlg_choice_node[i] == node { n += 1 }; i += 1 } + while i < rpg_st.dlg_choice_n { if rpg_st.dlg_choice_node[i] == node { n += 1 }; i += 1 } return n } # Dialog.choose(e, i) — take the i-th choice (veto-able); advance or end. -@Namespace(Dialog) function dialog_choose(e: int, idx: int) -> int { - dlg_ensure() +@Namespace(Dialog) function dialog_choose(rpg_st: mut RpgState, e: int, idx: int) -> int { + dlg_ensure(rpg_st) if dlg_get(e, "active") == 0 { return 0 } let node = dlg_get(e, "node") var seen = 0; var i = 0; var pick = -1 - while i < dlg_choice_n { - if dlg_choice_node[i] == node { - if seen == idx { pick = i; i = dlg_choice_n } else { seen += 1 } + while i < rpg_st.dlg_choice_n { + if rpg_st.dlg_choice_node[i] == node { + if seen == idx { pick = i; i = rpg_st.dlg_choice_n } else { seen += 1 } } i += 1 } if pick < 0 { return 0 } if emit DialogChoice(e: e, node: node, choice: idx) != 0 { return 0 } - let nxt = dlg_choice_next[pick] + let nxt = rpg_st.dlg_choice_next[pick] if nxt < 0 { dlg_set(e, "active", 0) emit DialogEnded(e: e) diff --git a/packages/ludic.rpg/inventory.ludic b/packages/ludic.rpg/inventory.ludic index fbb193f2..cf188a28 100644 --- a/packages/ludic.rpg/inventory.ludic +++ b/packages/ludic.rpg/inventory.ludic @@ -10,40 +10,70 @@ import "ludic.gameplay/stats.ludic" const INV_MAX: int = 256 # ---- item registry -------------------------------------------------------- -var item_reg: Dict = null -var item_n: int = 0 -var item_stackmax: words = null -var item_weight: words = null -var item_value: words = null +state RpgState { + recipe_reg: Dict = null # recipe name -> id + recipe_n: int = 0 + recipe_out: pointers = null # output item name + recipe_outqty: words = null + ing_n: int = 0 + ing_rid: words = null # owning recipe id + ing_name: pointers = null # ingredient item name + ing_qty: words = null + dlg_reg: Dict = null # tree name -> tree id + dlg_tree_n: int = 0 + dlg_tree_start: words = null # first node id per tree + dlg_node_n: int = 0 + dlg_node_tree: words = null + dlg_node_text: pointers = null + dlg_choice_n: int = 0 + dlg_choice_node: words = null # owning node + dlg_choice_label: pointers = null + dlg_choice_next: words = null # next node id, or -1 to end + item_reg: Dict = null + item_n: int = 0 + item_stackmax: words = null + item_weight: words = null + item_value: words = null + inv_counts: Dict = null + quest_reg: Dict = null # quest name -> id + quest_n: int = 0 + quest_state: words = null + obj_n: int = 0 + obj_qid: words = null # owning quest id + obj_kind: words = null + obj_target: words = null # a target id/tag (kind-specific; 0 = any) + obj_need: words = null + obj_have: words = null + quest_flags: Dict = null # global flag store +} -function item_ensure() -> void { - if item_reg == null { - item_reg = Dict.new() - item_stackmax = words(INV_MAX); item_weight = words(INV_MAX); item_value = words(INV_MAX) +function item_ensure(rpg_st: mut RpgState) -> void { + if rpg_st.item_reg == null { + rpg_st.item_reg = Dict.new() + rpg_st.item_stackmax = words(INV_MAX); rpg_st.item_weight = words(INV_MAX); rpg_st.item_value = words(INV_MAX) } } # Item.def(name, stack_max, weight, value) — register an item template. Returns id. -@Namespace(Item) function item_def(name: pointer, stack_max: int, weight: int, value: int) -> int { - item_ensure() - if item_n >= INV_MAX { return -1 } - let id = item_n - item_stackmax[id] = stack_max; item_weight[id] = weight; item_value[id] = value - item_n += 1 - Dict.set(item_reg, name, id) +@Namespace(Item) function item_def(rpg_st: mut RpgState, name: pointer, stack_max: int, weight: int, value: int) -> int { + item_ensure(rpg_st) + if rpg_st.item_n >= INV_MAX { return -1 } + let id = rpg_st.item_n + rpg_st.item_stackmax[id] = stack_max; rpg_st.item_weight[id] = weight; rpg_st.item_value[id] = value + rpg_st.item_n += 1 + Dict.set(rpg_st.item_reg, name, id) return id } -@Namespace(Item) function item_id(name: pointer) -> int { item_ensure(); return Dict.get_or(item_reg, name, -1) } -@Namespace(Item) function item_value_of(name: pointer) -> int { - let id = item_id(name); if id < 0 { return 0 } - return item_value[id] +@Namespace(Item) function item_id(rpg_st: mut RpgState, name: pointer) -> int { item_ensure(rpg_st); return Dict.get_or(rpg_st.item_reg, name, -1) } +@Namespace(Item) function item_value_of(rpg_st: mut RpgState, name: pointer) -> int { + let id = item_id(rpg_st, name); if id < 0 { return 0 } + return rpg_st.item_value[id] } # ---- per-owner counts ----------------------------------------------------- property Inventory { gold: int = 0, weight_max: int = 0, slots: int = 0 } -var inv_counts: Dict = null -function inv_ensure() -> void { if inv_counts == null { inv_counts = Dict.new() } } +function inv_ensure(rpg_st: mut RpgState) -> void { if rpg_st.inv_counts == null { rpg_st.inv_counts = Dict.new() } } function inv_key(e: int, name: pointer) -> pointer { return `{e}/{name}` } event ItemAdded { e: int = 0, item: int = 0, qty: int = 0 } # item = registry id @@ -51,36 +81,36 @@ event ItemRemoved { e: int = 0, item: int = 0, qty: int = 0 } event cancellable ItemUse { e: int = 0, item: int = 0 } # veto a use (e.g. cursed / requirement) # Inventory.add(e, name, n) — add n of an item; emits ItemAdded. -@Namespace(Inventory) function inventory_add(e: int, name: pointer, n: int) -> void { - inv_ensure() +@Namespace(Inventory) function inventory_add(rpg_st: mut RpgState, e: int, name: pointer, n: int) -> void { + inv_ensure(rpg_st) let k = inv_key(e, name) - Dict.set(inv_counts, k, Dict.get_or(inv_counts, k, 0) + n) - emit ItemAdded(e: e, item: item_id(name), qty: n) + Dict.set(rpg_st.inv_counts, k, Dict.get_or(rpg_st.inv_counts, k, 0) + n) + emit ItemAdded(e: e, item: item_id(rpg_st, name), qty: n) } # Inventory.count(e, name) — how many the owner holds. -@Namespace(Inventory) function inventory_count(e: int, name: pointer) -> int { - inv_ensure() - return Dict.get_or(inv_counts, inv_key(e, name), 0) +@Namespace(Inventory) function inventory_count(rpg_st: mut RpgState, e: int, name: pointer) -> int { + inv_ensure(rpg_st) + return Dict.get_or(rpg_st.inv_counts, inv_key(e, name), 0) } -@Namespace(Inventory) function inventory_has(e: int, name: pointer) -> int { - if inventory_count(e, name) > 0 { return 1 } +@Namespace(Inventory) function inventory_has(rpg_st: mut RpgState, e: int, name: pointer) -> int { + if inventory_count(rpg_st, e, name) > 0 { return 1 } return 0 } # Inventory.remove(e, name, n) — remove up to n; returns 1 if it had enough. -@Namespace(Inventory) function inventory_remove(e: int, name: pointer, n: int) -> int { - inv_ensure() +@Namespace(Inventory) function inventory_remove(rpg_st: mut RpgState, e: int, name: pointer, n: int) -> int { + inv_ensure(rpg_st) let k = inv_key(e, name) - let have = Dict.get_or(inv_counts, k, 0) + let have = Dict.get_or(rpg_st.inv_counts, k, 0) if have < n { return 0 } - Dict.set(inv_counts, k, have - n) - emit ItemRemoved(e: e, item: item_id(name), qty: n) + Dict.set(rpg_st.inv_counts, k, have - n) + emit ItemRemoved(e: e, item: item_id(rpg_st, name), qty: n) return 1 } # Inventory.use(e, name) — consume one item unless a listener vetoes ItemUse. -@Namespace(Inventory) function inventory_use(e: int, name: pointer) -> int { - if inventory_count(e, name) <= 0 { return 0 } - if emit ItemUse(e: e, item: item_id(name)) != 0 { return 0 } - return inventory_remove(e, name, 1) +@Namespace(Inventory) function inventory_use(rpg_st: mut RpgState, e: int, name: pointer) -> int { + if inventory_count(rpg_st, e, name) <= 0 { return 0 } + if emit ItemUse(e: e, item: item_id(rpg_st, name)) != 0 { return 0 } + return inventory_remove(rpg_st, e, name, 1) } # Inventory.gold / add_gold / spend_gold @Namespace(Inventory) function inventory_gold(e: int) -> int { diff --git a/packages/ludic.rpg/quests.ludic b/packages/ludic.rpg/quests.ludic index 3e6105e7..277ec1cb 100644 --- a/packages/ludic.rpg/quests.ludic +++ b/packages/ludic.rpg/quests.ludic @@ -14,23 +14,13 @@ const QUEST_MAX: int = 128 const OBJ_MAX: int = 512 -var quest_reg: Dict = null # quest name -> id -var quest_n: int = 0 -var quest_state: words = null -var obj_n: int = 0 -var obj_qid: words = null # owning quest id -var obj_kind: words = null -var obj_target: words = null # a target id/tag (kind-specific; 0 = any) -var obj_need: words = null -var obj_have: words = null -var quest_flags: Dict = null # global flag store -function quest_ensure() -> void { - if quest_reg == null { - quest_reg = Dict.new(); quest_flags = Dict.new() - quest_state = words(QUEST_MAX) - obj_qid = words(OBJ_MAX); obj_kind = words(OBJ_MAX); obj_target = words(OBJ_MAX) - obj_need = words(OBJ_MAX); obj_have = words(OBJ_MAX) +function quest_ensure(rpg_st: mut RpgState) -> void { + if rpg_st.quest_reg == null { + rpg_st.quest_reg = Dict.new(); rpg_st.quest_flags = Dict.new() + rpg_st.quest_state = words(QUEST_MAX) + rpg_st.obj_qid = words(OBJ_MAX); rpg_st.obj_kind = words(OBJ_MAX); rpg_st.obj_target = words(OBJ_MAX) + rpg_st.obj_need = words(OBJ_MAX); rpg_st.obj_have = words(OBJ_MAX) } } @@ -39,48 +29,48 @@ event QuestCompleted { quest: int = 0 } event ObjectiveProgressed { quest: int = 0, objective: int = 0, have: int = 0, need: int = 0 } # Quest.define(name) — register a quest (state hidden). Returns id. -@Namespace(Quest) function quest_define(name: pointer) -> int { - quest_ensure() - if quest_n >= QUEST_MAX { return -1 } - let id = quest_n - quest_state[id] = 0 - quest_n += 1 - Dict.set(quest_reg, name, id) +@Namespace(Quest) function quest_define(rpg_st: mut RpgState, name: pointer) -> int { + quest_ensure(rpg_st) + if rpg_st.quest_n >= QUEST_MAX { return -1 } + let id = rpg_st.quest_n + rpg_st.quest_state[id] = 0 + rpg_st.quest_n += 1 + Dict.set(rpg_st.quest_reg, name, id) return id } -@Namespace(Quest) function quest_id(name: pointer) -> int { quest_ensure(); return Dict.get_or(quest_reg, name, -1) } +@Namespace(Quest) function quest_id(rpg_st: mut RpgState, name: pointer) -> int { quest_ensure(rpg_st); return Dict.get_or(rpg_st.quest_reg, name, -1) } # Quest.objective(quest_name, kind, target, need) — add an objective. -@Namespace(Quest) function quest_objective(quest_name: pointer, kind: int, target: int, need: int) -> int { - quest_ensure() - let qid = quest_id(quest_name) +@Namespace(Quest) function quest_objective(rpg_st: mut RpgState, quest_name: pointer, kind: int, target: int, need: int) -> int { + quest_ensure(rpg_st) + let qid = quest_id(rpg_st, quest_name) if qid < 0 { return -1 } - if obj_n >= OBJ_MAX { return -1 } - let id = obj_n - obj_qid[id] = qid; obj_kind[id] = kind; obj_target[id] = target - obj_need[id] = need; obj_have[id] = 0 - obj_n += 1 + if rpg_st.obj_n >= OBJ_MAX { return -1 } + let id = rpg_st.obj_n + rpg_st.obj_qid[id] = qid; rpg_st.obj_kind[id] = kind; rpg_st.obj_target[id] = target + rpg_st.obj_need[id] = need; rpg_st.obj_have[id] = 0 + rpg_st.obj_n += 1 return id } # Quest.start(name) — activate a quest (emits QuestStarted). -@Namespace(Quest) function quest_start(name: pointer) -> void { - quest_ensure() - let qid = quest_id(name) +@Namespace(Quest) function quest_start(rpg_st: mut RpgState, name: pointer) -> void { + quest_ensure(rpg_st) + let qid = quest_id(rpg_st, name) if qid < 0 { return } - quest_state[qid] = 2 + rpg_st.quest_state[qid] = 2 emit QuestStarted(quest: qid) } -@Namespace(Quest) function quest_state_of(name: pointer) -> int { - quest_ensure() - let qid = quest_id(name) +@Namespace(Quest) function quest_state_of(rpg_st: mut RpgState, name: pointer) -> int { + quest_ensure(rpg_st) + let qid = quest_id(rpg_st, name) if qid < 0 { return -1 } - return quest_state[qid] + return rpg_st.quest_state[qid] } # is every objective of a quest satisfied? -function quest_all_done(qid: int) -> int { +function quest_all_done(rpg_st: RpgState, qid: int) -> int { var i = 0 - while i < obj_n { - if obj_qid[i] == qid { if obj_have[i] < obj_need[i] { return 0 } } + while i < rpg_st.obj_n { + if rpg_st.obj_qid[i] == qid { if rpg_st.obj_have[i] < rpg_st.obj_need[i] { return 0 } } i += 1 } return 1 @@ -89,20 +79,20 @@ function quest_all_done(qid: int) -> int { # Quest.notify(kind, target, amount) — report a gameplay signal. Every ACTIVE # quest's matching objectives tick up (target 0 on the objective = "any"); a quest # whose objectives are all met auto-completes. -@Namespace(Quest) function quest_notify(kind: int, target: int, amount: int) -> void { - quest_ensure() +@Namespace(Quest) function quest_notify(rpg_st: mut RpgState, kind: int, target: int, amount: int) -> void { + quest_ensure(rpg_st) var i = 0 - while i < obj_n { - let qid = obj_qid[i] - if quest_state[qid] == 2 { - if obj_kind[i] == kind { - if (obj_target[i] == 0) or (obj_target[i] == target) { - if obj_have[i] < obj_need[i] { - obj_have[i] += amount - if obj_have[i] > obj_need[i] { obj_have[i] = obj_need[i] } - emit ObjectiveProgressed(quest: qid, objective: i, have: obj_have[i], need: obj_need[i]) - if quest_all_done(qid) == 1 { - quest_state[qid] = 3 + while i < rpg_st.obj_n { + let qid = rpg_st.obj_qid[i] + if rpg_st.quest_state[qid] == 2 { + if rpg_st.obj_kind[i] == kind { + if (rpg_st.obj_target[i] == 0) or (rpg_st.obj_target[i] == target) { + if rpg_st.obj_have[i] < rpg_st.obj_need[i] { + rpg_st.obj_have[i] += amount + if rpg_st.obj_have[i] > rpg_st.obj_need[i] { rpg_st.obj_have[i] = rpg_st.obj_need[i] } + emit ObjectiveProgressed(quest: qid, objective: i, have: rpg_st.obj_have[i], need: rpg_st.obj_need[i]) + if quest_all_done(rpg_st, qid) == 1 { + rpg_st.quest_state[qid] = 3 emit QuestCompleted(quest: qid) } } @@ -114,5 +104,5 @@ function quest_all_done(qid: int) -> int { } # ---- global flag store (branching / world state) -------------------------- -@Namespace(Quest) function quest_flag(name: pointer, value: int) -> void { quest_ensure(); Dict.set(quest_flags, name, value) } -@Namespace(Quest) function quest_flag_get(name: pointer) -> int { quest_ensure(); return Dict.get_or(quest_flags, name, 0) } +@Namespace(Quest) function quest_flag(rpg_st: mut RpgState, name: pointer, value: int) -> void { quest_ensure(rpg_st); Dict.set(rpg_st.quest_flags, name, value) } +@Namespace(Quest) function quest_flag_get(rpg_st: mut RpgState, name: pointer) -> int { quest_ensure(rpg_st); return Dict.get_or(rpg_st.quest_flags, name, 0) } diff --git a/packages/ludic.session/roster.ludic b/packages/ludic.session/roster.ludic index 9d56a989..3bf26e21 100644 --- a/packages/ludic.session/roster.ludic +++ b/packages/ludic.session/roster.ludic @@ -54,7 +54,7 @@ export function ply_leave(base_st: mut BaseState, session_st: mut SessionState, export function ply_leave_all(base_st: mut BaseState, session_st: mut SessionState) -> void { for p in 1 .. PLY_MAX { ply_leave(base_st, session_st, p) } } # what a remote player's machine reported this tick -export function ply_report(session_st: SessionState, p: int, x: float, y: float, z: float, yaw: float, speed: float) -> void { +export function ply_report(session_st: mut SessionState, p: int, x: float, y: float, z: float, yaw: float, speed: float) -> void { if p <= 0 or not ply_on(session_st, p) { return } let r = session_st.session__slot[p] r.x = x diff --git a/packages/ludic.shooter/shooter.ludic b/packages/ludic.shooter/shooter.ludic index 7170f36d..ca38bbba 100644 --- a/packages/ludic.shooter/shooter.ludic +++ b/packages/ludic.shooter/shooter.ludic @@ -69,76 +69,79 @@ event WeaponFired { owner: int = 0, def_id: int = 0 } # --------------------------------------------------------------------------- # Weapon registry (name-keyed, mod-friendly — add a gun with no code) # --------------------------------------------------------------------------- -var wpn_reg: Dict = null -var wpn_n: int = 0 -var wpn_rate: words = null -var wpn_dmg: words = null -var wpn_speed: words = null -var wpn_spread: words = null -var wpn_pellets: words = null -var wpn_pattern: words = null -var wpn_ttl: words = null -var wpn_pierce: words = null -var wpn_homing: words = null -var wpn_color: words = null +state ShooterState { + wpn_reg: Dict = null + wpn_n: int = 0 + wpn_rate: words = null + wpn_dmg: words = null + wpn_speed: words = null + wpn_spread: words = null + wpn_pellets: words = null + wpn_pattern: words = null + wpn_ttl: words = null + wpn_pierce: words = null + wpn_homing: words = null + wpn_color: words = null + wpn_rate_base: words = null + wpn_fire_color: int = 0xffffff # the colour of the weapon being fired (sh_fire) +} -function wpn_ensure() -> void { - if wpn_reg == null { - wpn_reg = Dict.new() - wpn_rate = words(WPN_MAX); wpn_dmg = words(WPN_MAX); wpn_speed = words(WPN_MAX) - wpn_spread = words(WPN_MAX); wpn_pellets = words(WPN_MAX); wpn_pattern = words(WPN_MAX) - wpn_ttl = words(WPN_MAX); wpn_pierce = words(WPN_MAX); wpn_homing = words(WPN_MAX) - wpn_color = words(WPN_MAX); wpn_rate_base = words(WPN_MAX) +function wpn_ensure(shooter_st: mut ShooterState) -> void { + if shooter_st.wpn_reg == null { + shooter_st.wpn_reg = Dict.new() + shooter_st.wpn_rate = words(WPN_MAX); shooter_st.wpn_dmg = words(WPN_MAX); shooter_st.wpn_speed = words(WPN_MAX) + shooter_st.wpn_spread = words(WPN_MAX); shooter_st.wpn_pellets = words(WPN_MAX); shooter_st.wpn_pattern = words(WPN_MAX) + shooter_st.wpn_ttl = words(WPN_MAX); shooter_st.wpn_pierce = words(WPN_MAX); shooter_st.wpn_homing = words(WPN_MAX) + shooter_st.wpn_color = words(WPN_MAX); shooter_st.wpn_rate_base = words(WPN_MAX) } } # Weapon.def(name, fire_rate, damage, speed, spread, pellets, pattern) — register a # gun. pattern: 0 single/aimed, 1 spread cone, 2 ring, 3 spiral. Returns the def id. -@Namespace(Weapon) function weapon_def(name: pointer, fire_rate: int, damage: int, speed: int, spread: int, pellets: int, pattern: int) -> int { - wpn_ensure() - if wpn_n >= WPN_MAX { return -1 } - let id = wpn_n - wpn_rate[id] = fire_rate; wpn_dmg[id] = damage; wpn_speed[id] = speed - wpn_spread[id] = spread; wpn_pellets[id] = pellets; wpn_pattern[id] = pattern - wpn_ttl[id] = 120 - wpn_color[id] = 0xffffff - wpn_rate_base[id] = fire_rate - wpn_n += 1 - Dict.set(wpn_reg, name, id) +@Namespace(Weapon) function weapon_def(shooter_st: mut ShooterState, name: pointer, fire_rate: int, damage: int, speed: int, spread: int, pellets: int, pattern: int) -> int { + wpn_ensure(shooter_st) + if shooter_st.wpn_n >= WPN_MAX { return -1 } + let id = shooter_st.wpn_n + shooter_st.wpn_rate[id] = fire_rate; shooter_st.wpn_dmg[id] = damage; shooter_st.wpn_speed[id] = speed + shooter_st.wpn_spread[id] = spread; shooter_st.wpn_pellets[id] = pellets; shooter_st.wpn_pattern[id] = pattern + shooter_st.wpn_ttl[id] = 120 + shooter_st.wpn_color[id] = 0xffffff + shooter_st.wpn_rate_base[id] = fire_rate + shooter_st.wpn_n += 1 + Dict.set(shooter_st.wpn_reg, name, id) return id } -@Namespace(Weapon) function weapon_id(name: pointer) -> int { - wpn_ensure() - return Dict.get_or(wpn_reg, name, -1) +@Namespace(Weapon) function weapon_id(shooter_st: mut ShooterState, name: pointer) -> int { + wpn_ensure(shooter_st) + return Dict.get_or(shooter_st.wpn_reg, name, -1) } # Weapon.set_rate(id, frames) — frames between shots (a fire-rate upgrade changes it in place). -@Namespace(Weapon) function weapon_set_rate(id: int, frames: int) -> void { - wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_rate[id] = frames } +@Namespace(Weapon) function weapon_set_rate(shooter_st: mut ShooterState, id: int, frames: int) -> void { + wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_rate[id] = frames } } -@Namespace(Weapon) function weapon_rate(id: int) -> int { - wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { return wpn_rate[id] }; return 0 +@Namespace(Weapon) function weapon_rate(shooter_st: mut ShooterState, id: int) -> int { + wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { return shooter_st.wpn_rate[id] }; return 0 } # Weapon.reset(id) — back to the definition: no pierce, no homing, the defined fire rate. -var wpn_rate_base: words = null -@Namespace(Weapon) function weapon_reset(id: int) -> void { - wpn_ensure(); if (id < 0) or (id >= WPN_MAX) { return } - wpn_pierce[id] = 0; wpn_homing[id] = 0; wpn_rate[id] = wpn_rate_base[id] +@Namespace(Weapon) function weapon_reset(shooter_st: mut ShooterState, id: int) -> void { + wpn_ensure(shooter_st); if (id < 0) or (id >= WPN_MAX) { return } + shooter_st.wpn_pierce[id] = 0; shooter_st.wpn_homing[id] = 0; shooter_st.wpn_rate[id] = shooter_st.wpn_rate_base[id] } # Weapon.set_color(id, color) — the colour the engine draws this weapon's shots in. -@Namespace(Weapon) function weapon_set_color(id: int, color: int) -> void { - wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_color[id] = color } +@Namespace(Weapon) function weapon_set_color(shooter_st: mut ShooterState, id: int, color: int) -> void { + wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_color[id] = color } } # Weapon.set_ttl(id, frames) — projectile lifetime for a weapon (range control). -@Namespace(Weapon) function weapon_set_ttl(id: int, frames: int) -> void { - wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_ttl[id] = frames } +@Namespace(Weapon) function weapon_set_ttl(shooter_st: mut ShooterState, id: int, frames: int) -> void { + wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_ttl[id] = frames } } # Weapon.set_pierce(id, n) — a shot passes through n targets before it dies. -@Namespace(Weapon) function weapon_set_pierce(id: int, n: int) -> void { - wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_pierce[id] = n } +@Namespace(Weapon) function weapon_set_pierce(shooter_st: mut ShooterState, id: int, n: int) -> void { + wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_pierce[id] = n } } # Weapon.set_homing(id, deg) — max degrees/frame a shot turns toward an enemy. -@Namespace(Weapon) function weapon_set_homing(id: int, deg: int) -> void { - wpn_ensure(); if (id >= 0) and (id < WPN_MAX) { wpn_homing[id] = deg } +@Namespace(Weapon) function weapon_set_homing(shooter_st: mut ShooterState, id: int, deg: int) -> void { + wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_homing[id] = deg } } # --------------------------------------------------------------------------- @@ -493,7 +496,7 @@ function sh_angle_diff(from: int, to: int) -> int { } # spawn one projectile travelling at `deg` degrees from (px,py). -function sh_spawn_bullet(owner: int, fac: int, px: int, py: int, deg: int, speed: int, dmg: int, ttl: int, pierce: int, homing: int) -> int { +function sh_spawn_bullet(shooter_st: ShooterState, owner: int, fac: int, px: int, py: int, deg: int, speed: int, dmg: int, ttl: int, pierce: int, homing: int) -> int { let m = World.model_id("Bullet") if m < 0 { return -1 } let b = World.spawn(m) @@ -518,21 +521,20 @@ function sh_spawn_bullet(owner: int, fac: int, px: int, py: int, deg: int, speed World.set(b, PC, World.field_id(PC, "h"), 4) } # announce it: a listener may veto (cancel -> despawn) or tweak the Projectile. - pr_set(b, "color", wpn_fire_color) + pr_set(b, "color", shooter_st.wpn_fire_color) if emit ProjectileSpawned(proj: b, owner: owner, damage: dmg) != 0 { despawn b; return -1 } return b } -var wpn_fire_color: int = 0xffffff # the colour of the weapon being fired (sh_fire) # =========================================================================== # esys_weapon (Update) — fire-rate gating + pattern emission. Reads the owner's # TopDown.aim_angle + Weapon.want-fire-equivalent (TopDown.want_fire); one impl # serves player and NPC. # =========================================================================== -@EngineSystem(Weapon, Update) function esys_weapon() -> void { +@EngineSystem(Weapon, Update) function esys_weapon(shooter_st: mut ShooterState) -> void { let P = World.prop_id("Weapon") if P < 0 { return } - wpn_ensure() + wpn_ensure(shooter_st) var e = World.query_next(P, 0) while e >= 0 { let cd = World.get(e, P, World.field_id(P, "cooldown")) @@ -540,34 +542,34 @@ var wpn_fire_color: int = 0xffffff # the colour of the weapon being let fire = td_get(e, "want_fire") if (fire == 1) and (cd <= 0) { let id = World.get(e, P, World.field_id(P, "def_id")) - if (id >= 0) and (id < wpn_n) { - sh_fire(e, id) - World.set(e, P, World.field_id(P, "cooldown"), wpn_rate[id] * stats_field(e, "fire_rate_pct", 100) / 100) + if (id >= 0) and (id < shooter_st.wpn_n) { + sh_fire(shooter_st, e, id) + World.set(e, P, World.field_id(P, "cooldown"), shooter_st.wpn_rate[id] * stats_field(e, "fire_rate_pct", 100) / 100) } } e = World.query_next(P, e + 1) } } -function sh_fire(owner: int, id: int) -> void { +function sh_fire(shooter_st: mut ShooterState, owner: int, id: int) -> void { let px = sh_cx(owner) let py = sh_cy(owner) let base = td_get(owner, "aim_angle") let fac = Faction.id_of(owner) - let speed = wpn_speed[id] - let dmg = wpn_dmg[id] - let spread = wpn_spread[id] - let pellets0 = wpn_pellets[id] - let pattern = wpn_pattern[id] - let ttl = wpn_ttl[id] - let pierce = wpn_pierce[id] - let homing = wpn_homing[id] - wpn_fire_color = wpn_color[id] + let speed = shooter_st.wpn_speed[id] + let dmg = shooter_st.wpn_dmg[id] + let spread = shooter_st.wpn_spread[id] + let pellets0 = shooter_st.wpn_pellets[id] + let pattern = shooter_st.wpn_pattern[id] + let ttl = shooter_st.wpn_ttl[id] + let pierce = shooter_st.wpn_pierce[id] + let homing = shooter_st.wpn_homing[id] + shooter_st.wpn_fire_color = shooter_st.wpn_color[id] var pellets = pellets0; if pellets < 1 { pellets = 1 } emit WeaponFired(owner: owner, def_id: id) if pattern == WeaponPattern.Ring { # pellets around 360 var i = 0 - while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 } + while i < pellets { sh_spawn_bullet(shooter_st, owner, fac, px, py, sh_wrap_deg(base + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 } return } if pattern == WeaponPattern.Spiral { # ring + a rotating phase @@ -575,18 +577,18 @@ function sh_fire(owner: int, id: int) -> void { let fph = World.field_id(PW, "phase") var ph = 0; if fph >= 0 { ph = World.get(owner, PW, fph) } var i = 0 - while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + ph + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 } + while i < pellets { sh_spawn_bullet(shooter_st, owner, fac, px, py, sh_wrap_deg(base + ph + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 } if fph >= 0 { World.set(owner, PW, fph, sh_wrap_deg(ph + 15)) } return } # single / spread cone centred on the aim if pellets == 1 { - sh_spawn_bullet(owner, fac, px, py, base, speed, dmg, ttl, pierce, homing) + sh_spawn_bullet(shooter_st, owner, fac, px, py, base, speed, dmg, ttl, pierce, homing) } else { var i = 0 while i < pellets { let off = -(spread / 2) + i * spread / (pellets - 1) - sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + off), speed, dmg, ttl, pierce, homing) + sh_spawn_bullet(shooter_st, owner, fac, px, py, sh_wrap_deg(base + off), speed, dmg, ttl, pierce, homing) i += 1 } } @@ -727,8 +729,8 @@ event WaveCleared { spawner: int = 0, wave: int = 0 } @Namespace(TopDown) function topdown_aim(e: int, deg: int) -> void { td_set(e, "aim_angle", deg) } @Namespace(TopDown) function topdown_fire(e: int, on: int) -> void { td_set(e, "want_fire", on) } # equip a weapon by registry name. -@Namespace(Weapon) function weapon_equip(e: int, name: pointer) -> void { +@Namespace(Weapon) function weapon_equip(shooter_st: mut ShooterState, e: int, name: pointer) -> void { let P = World.prop_id("Weapon") if P < 0 { return } - World.set(e, P, World.field_id(P, "def_id"), weapon_id(name)) + World.set(e, P, World.field_id(P, "def_id"), weapon_id(shooter_st, name)) } diff --git a/packages/ludic.ui/act.ludic b/packages/ludic.ui/act.ludic index 65aae0b3..38276e58 100644 --- a/packages/ludic.ui/act.ludic +++ b/packages/ludic.ui/act.ludic @@ -51,20 +51,20 @@ function act_keyword(r: UiRd, word: string) -> bool { } return true } -function act_run(a: UiAct, e: UiEnv) -> void { +function act_run(ui_st: mut UiState, a: UiAct, e: UiEnv) -> void { for i in 0 .. len(a.kinds) { - if a.kinds[i] == A_SET { env_set(e, a.names[i], ui_eval(a.exprs[i], e)) } - if a.kinds[i] == A_CALL { ui_eval(a.exprs[i], e) } - if a.kinds[i] == A_EMIT { act_emit(a.names[i], e) } + if a.kinds[i] == A_SET { env_set(ui_st, e, a.names[i], ui_eval(ui_st, a.exprs[i], e)) } + if a.kinds[i] == A_CALL { ui_eval(ui_st, a.exprs[i], e) } + if a.kinds[i] == A_EMIT { act_emit(ui_st, a.names[i], e) } } } -function act_emit(name: string, e: UiEnv) -> void { +function act_emit(ui_st: mut UiState, name: string, e: UiEnv) -> void { var at = e while at != null { if at.on_names != null { for i in 0 .. len(at.on_names) { if at.on_names[i] == name { - act_run(at.on_acts[i], at.on_envs[i]) + act_run(ui_st, at.on_acts[i], at.on_envs[i]) return } } diff --git a/packages/ludic.ui/anim.ludic b/packages/ludic.ui/anim.ludic index 99a448b0..22932f41 100644 --- a/packages/ludic.ui/anim.ludic +++ b/packages/ludic.ui/anim.ludic @@ -6,17 +6,13 @@ export property UiKeyframes { stops: []float = null rules: []UiRule = null } -var an_frames: []UiKeyframes = new []UiKeyframes -var an_keys: []string = new []string -var an_start: []float = new []float -var an_frame: int = 0 # the renderer's clock in seconds, or the frames shown at sixty a second (a test, a still picture) -function an_now() -> float { - if ui_be != null and ui_be.now != null { return ui_be.now() } - return float(an_frame) / 60.0 +function an_now(ui_st: UiState) -> float { + if ui_st.ui_be != null and ui_st.ui_be.now != null { return ui_st.ui_be.now() } + return float(ui_st.an_frame) / 60.0 } # @keyframes NAME { from {...} 50% {...} to {...} } -function an_read(name: string, body: string, err: []string) -> void { +function an_read(ui_st: mut UiState, name: string, body: string, err: []string) -> void { let k = new UiKeyframes k.name = tpl_words(name) k.stops = new []float @@ -34,40 +30,40 @@ function an_read(name: string, body: string, err: []string) -> void { push(k.stops, at) push(k.rules, decls) } - push(an_frames, k) + push(ui_st.an_frames, k) } -function an_find(name: string) -> UiKeyframes { - for i in 0 .. len(an_frames) { - if an_frames[i].name == name { return an_frames[i] } +function an_find(ui_st: UiState, name: string) -> UiKeyframes { + for i in 0 .. len(ui_st.an_frames) { + if ui_st.an_frames[i].name == name { return ui_st.an_frames[i] } } return null } # when this element's animation began: the first frame it was seen with it -function an_began(key: string) -> float { - for i in 0 .. len(an_keys) { - if an_keys[i] == key { return an_start[i] } +function an_began(ui_st: mut UiState, key: string) -> float { + for i in 0 .. len(ui_st.an_keys) { + if ui_st.an_keys[i] == key { return ui_st.an_start[i] } } - push(an_keys, key) - push(an_start, an_now()) - return an_now() + push(ui_st.an_keys, key) + push(ui_st.an_start, an_now(ui_st)) + return an_now(ui_st) } # the element's animation at this moment: each property between the stops around it -function an_apply(n: UiNode) -> void { +function an_apply(ui_st: mut UiState, n: UiNode) -> void { if n.anim == "" or n.anim_dur <= 0.0 { return } - let k = an_find(n.anim) + let k = an_find(ui_st, n.anim) if k == null { - ui_err(`animation {n.anim}: there is no @keyframes of that name`) + ui_err(ui_st, `animation {n.anim}: there is no @keyframes of that name`) return } - var t = (an_now() - an_began(`{n.key}@{n.anim}`)) / n.anim_dur + var t = (an_now(ui_st) - an_began(ui_st, `{n.key}@{n.anim}`)) / n.anim_dur if n.anim_loop { let lap = int(t) t = t - float(lap) if n.anim_alt and lap % 2 == 1 { t = 1.0 - t } } else { t = Math.clamp(t, 0.0, 1.0) } - an_at(n, k, t) + an_at(ui_st, n, k, t) } -function an_at(n: UiNode, k: UiKeyframes, t: float) -> void { +function an_at(ui_st: mut UiState, n: UiNode, k: UiKeyframes, t: float) -> void { var lo = 0 var hi = len(k.stops) - 1 for i in 0 .. len(k.stops) { @@ -79,11 +75,11 @@ function an_at(n: UiNode, k: UiKeyframes, t: float) -> void { let a = k.rules[lo] let b = k.rules[hi] for i in 0 .. len(a.keys) { - let va = el_num(vl_text(ui_eval(a.vals[i], n.env))) + let va = el_num(vl_text(ui_eval(ui_st, a.vals[i], n.env))) var vb = va for j in 0 .. len(b.keys) { - if b.keys[j] == a.keys[i] { vb = el_num(vl_text(ui_eval(b.vals[j], n.env))) } + if b.keys[j] == a.keys[i] { vb = el_num(vl_text(ui_eval(ui_st, b.vals[j], n.env))) } } - el_apply(n, a.keys[i], Value.float(va + (vb - va) * f)) + el_apply(ui_st, n, a.keys[i], Value.float(va + (vb - va) * f)) } } diff --git a/packages/ludic.ui/attr.ludic b/packages/ludic.ui/attr.ludic index 62510a58..c7ca60cd 100644 --- a/packages/ludic.ui/attr.ludic +++ b/packages/ludic.ui/attr.ludic @@ -3,21 +3,21 @@ const AT_ALIAS: string = " width=w height=h min-width=min-w max-width=max-w min-height=min-h max-height=max-h padding=pad background=bg background-color=bg font-size=size opacity=alpha flex-grow=grow flex-shrink=shrink flex-direction=dir flex-wrap=wrap justify-content=justify align-items=align align-self=self row-gap=gap column-gap=gap text-align=talign " const AT_UNDRAWN: string = " font-weight font-family font-style box-shadow cursor transition animation letter-spacing text-decoration " # a stylesheet's property: one that means nothing is said -function el_attr(n: UiNode, key: string, v: Val) -> void { - if el_apply(n, key, v) { return } - if lk_in(AT_UNDRAWN, at_alias(key)) { ui_err(`{key}: this renderer does not draw it`) } else { ui_err(`{key}: there is no such property`) } +function el_attr(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> void { + if el_apply(ui_st, n, key, v) { return } + if lk_in(AT_UNDRAWN, at_alias(key)) { ui_err(ui_st, `{key}: this renderer does not draw it`) } else { ui_err(ui_st, `{key}: there is no such property`) } } # an element's attribute: a property when it names one, otherwise only there for [selectors] -function el_apply(n: UiNode, key: string, v0: Val) -> bool { +function el_apply(ui_st: mut UiState, n: UiNode, key: string, v0: Val) -> bool { let k = at_alias(key) - el_cur = n - el_key = k + ui_st.el_cur = n + ui_st.el_key = k if len(k) > 2 and k[0..2] == "--" { cs_var_set(n, k, vl_text(v0)) return true } - let v = cs_vars_in(n, v0) - return el_size(n, k, v) or el_box(n, k, v) or el_flex(n, k, v) or el_look(n, k, v) or el_place_prop(n, k, v) or el_edge(n, k, v) or el_more(n, k, v) + let v = cs_vars_in(ui_st, n, v0) + return el_size(ui_st, n, k, v) or el_box(ui_st, n, k, v) or el_flex(n, k, v) or el_look(ui_st, n, k, v) or el_place_prop(ui_st, n, k, v) or el_edge(ui_st, n, k, v) or el_more(ui_st, n, k, v) } function at_alias(key: string) -> string { let pat = ` {key}=` @@ -31,8 +31,8 @@ function at_alias(key: string) -> string { } return key } -function el_size(n: UiNode, key: string, v: Val) -> bool { - if key == "w" or key == "h" { el_dim(n, key == "w", v) } else if key == "min-w" { n.min_w = el_len(v) } else if key == "max-w" { n.max_w = el_len(v) } else if key == "min-h" { n.min_h = el_len(v) } else if key == "max-h" { n.max_h = el_len(v) } else if key == "gap" { n.gap = el_len(v) } else { return false } +function el_size(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { + if key == "w" or key == "h" { el_dim(ui_st, n, key == "w", v) } else if key == "min-w" { n.min_w = el_len(ui_st, v) } else if key == "max-w" { n.max_w = el_len(ui_st, v) } else if key == "min-h" { n.min_h = el_len(ui_st, v) } else if key == "max-h" { n.max_h = el_len(ui_st, v) } else if key == "gap" { n.gap = el_len(ui_st, v) } else { return false } return true } function el_flex(n: UiNode, key: string, v: Val) -> bool { @@ -43,33 +43,33 @@ function el_flex(n: UiNode, key: string, v: Val) -> bool { } else if key == "grow" { n.grow = el_num(s) } else if key == "flex" { el_flex_short(n, s) } else if key == "wrap" { n.wrap = s == "wrap" or (vl_truthy(v) and s != "false" and s != "nowrap") } else if key == "align" { n.align = el_place(v) } else if key == "self" { n.self = el_place(v) } else if key == "justify" { n.justify = el_place(v) } else if key == "talign" { n.talign = el_place(v) } else if key == "display" { n.hidden = s == "none" } else { return false } return true } -function el_look(n: UiNode, key: string, v: Val) -> bool { +function el_look(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { let s = vl_text(v) - if key == "size" { n.size = el_len(v) } else if key == "color" { - n.fg = ui_color(s) - n.fg_a = cl_alpha + if key == "size" { n.size = el_len(ui_st, v) } else if key == "color" { + n.fg = ui_color(ui_st, s) + n.fg_a = ui_st.cl_alpha } else if key == "bg" { - if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(s) } else if len(s) > 4 and s[0..4] == "url(" { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" { + if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(ui_st, s) } else if len(s) > 4 and s[0..4] == "url(" { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" { n.bg = 0 n.bg_a = 0.0 } else { - n.bg = ui_color(s) - n.bg_a = cl_alpha + n.bg = ui_color(ui_st, s) + n.bg_a = ui_st.cl_alpha } } else if key == "alpha" { n.alpha = el_num(s) } else if key == "enabled" { n.enabled = vl_truthy(v) and s != "false" } else if key == "border-color" { - n.border_c = ui_color(s) - n.border_a = cl_alpha - } else if key == "border-width" { el_sides_border(n, el_len(v)) } else if key == "border" { el_border(n, s) } else if key == "border-top" or key == "border-right" or key == "border-bottom" or key == "border-left" { el_border_side(n, key, s) } else { return false } + n.border_c = ui_color(ui_st, s) + n.border_a = ui_st.cl_alpha + } else if key == "border-width" { el_sides_border(n, el_len(ui_st, v)) } else if key == "border" { el_border(ui_st, n, s) } else if key == "border-top" or key == "border-right" or key == "border-bottom" or key == "border-left" { el_border_side(ui_st, n, key, s) } else { return false } return true } # border: 1 #ffcc00, or 1px solid accent -function el_border(n: UiNode, s: string) -> void { +function el_border(ui_st: mut UiState, n: UiNode, s: string) -> void { let parts = lss_split(s, 32) for i in 0 .. len(parts) { let p = parts[i] - if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { el_sides_border(n, el_unit(p)) } else if p == "none" { el_sides_border(n, 0.0) } else if len(p) > 0 and p != "solid" { - n.border_c = ui_color(p) - n.border_a = cl_alpha + if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { el_sides_border(n, el_unit(ui_st, p)) } else if p == "none" { el_sides_border(n, 0.0) } else if len(p) > 0 and p != "solid" { + n.border_c = ui_color(ui_st, p) + n.border_a = ui_st.cl_alpha } } if n.border_c == UI_NONE { n.border_c = n.rfg } diff --git a/packages/ludic.ui/attr_border.ludic b/packages/ludic.ui/attr_border.ludic index dec8b96c..462bde71 100644 --- a/packages/ludic.ui/attr_border.ludic +++ b/packages/ludic.ui/attr_border.ludic @@ -8,12 +8,12 @@ function el_sides_border(n: UiNode, w: float) -> void { n.bl = w } # border-bottom: 3px solid var(--accent) - one side's width, and the colour for the border -function el_border_side(n: UiNode, key: string, s: string) -> void { +function el_border_side(ui_st: mut UiState, n: UiNode, key: string, s: string) -> void { let keep_t = n.bt let keep_r = n.br let keep_b = n.bb let keep_l = n.bl - el_border(n, s) + el_border(ui_st, n, s) let w = n.bt n.bt = keep_t n.br = keep_r diff --git a/packages/ludic.ui/attr_box.ludic b/packages/ludic.ui/attr_box.ludic index 06dca9a3..6b072ff5 100644 --- a/packages/ludic.ui/attr_box.ludic +++ b/packages/ludic.ui/attr_box.ludic @@ -1,50 +1,46 @@ # attr_box.ludic - padding and margin (one to four lengths, as CSS writes them, or one side by # name), lengths with px or %, and the words that place things -function el_box(n: UiNode, key: string, v: Val) -> bool { +function el_box(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { if key == "pad" { - el_sides(v) - n.pt = el_s0 - n.pr = el_s1 - n.pb = el_s2 - n.pl = el_s3 + el_sides(ui_st, v) + n.pt = ui_st.el_s0 + n.pr = ui_st.el_s1 + n.pb = ui_st.el_s2 + n.pl = ui_st.el_s3 } else if key == "margin" { - el_sides(v) - n.mt = el_s0 - n.mr = el_s1 - n.mb = el_s2 - n.ml = el_s3 - } else if key == "padding-top" { n.pt = el_len(v) } else if key == "padding-right" { n.pr = el_len(v) } else if key == "padding-bottom" { n.pb = el_len(v) } else if key == "padding-left" { n.pl = el_len(v) } else if key == "margin-top" { n.mt = el_len(v) } else if key == "margin-right" { n.mr = el_len(v) } else if key == "margin-bottom" { n.mb = el_len(v) } else if key == "margin-left" { n.ml = el_len(v) } else { return false } + el_sides(ui_st, v) + n.mt = ui_st.el_s0 + n.mr = ui_st.el_s1 + n.mb = ui_st.el_s2 + n.ml = ui_st.el_s3 + } else if key == "padding-top" { n.pt = el_len(ui_st, v) } else if key == "padding-right" { n.pr = el_len(ui_st, v) } else if key == "padding-bottom" { n.pb = el_len(ui_st, v) } else if key == "padding-left" { n.pl = el_len(ui_st, v) } else if key == "margin-top" { n.mt = el_len(ui_st, v) } else if key == "margin-right" { n.mr = el_len(ui_st, v) } else if key == "margin-bottom" { n.mb = el_len(ui_st, v) } else if key == "margin-left" { n.ml = el_len(ui_st, v) } else { return false } return true } -var el_s0: float = 0.0 -var el_s1: float = 0.0 -var el_s2: float = 0.0 -var el_s3: float = 0.0 # top right bottom left out of 1 to 4 lengths -function el_sides(v: Val) -> void { +function el_sides(ui_st: mut UiState, v: Val) -> void { let parts = new []float - if vl_num(v) { push(parts, el_px(Value.as_float(v))) } else { + if vl_num(v) { push(parts, el_px(ui_st, Value.as_float(v))) } else { let words = el_words(vl_text(v)) for i in 0 .. len(words) { - if len(words[i]) > 0 { push(parts, el_word_len(words[i])) } + if len(words[i]) > 0 { push(parts, el_word_len(ui_st, words[i])) } } } while len(parts) < 4 { if len(parts) == 0 { push(parts, 0.0) } else if len(parts) == 1 { push(parts, parts[0]) } else if len(parts) == 2 { push(parts, parts[0]) } else { push(parts, parts[1]) } } - el_s0 = parts[0] - el_s1 = parts[1] - el_s2 = parts[2] - el_s3 = parts[3] + ui_st.el_s0 = parts[0] + ui_st.el_s1 = parts[1] + ui_st.el_s2 = parts[2] + ui_st.el_s3 = parts[3] } # a length: a number, 10px, 1.5em, 2rem, 50vw, calc(...), fill, fit or auto (a percentage is el_pct's) -function el_len(v: Val) -> float { - if vl_num(v) { return el_px(Value.as_float(v)) } +function el_len(ui_st: mut UiState, v: Val) -> float { + if vl_num(v) { return el_px(ui_st, Value.as_float(v)) } let s = vl_text(v) if s == "fill" { return UI_FILL } - if ca_is(s) { return el_calc_len(s) } + if ca_is(s) { return el_calc_len(ui_st, s) } if s == "fit" or s == "auto" or (len(s) > 0 and s[len(s) - 1] == 37) { return 0.0 } - return el_unit(s) + return el_unit(ui_st, s) } function el_pct(v: Val) -> float { let s = vl_text(v) diff --git a/packages/ludic.ui/attr_edge.ludic b/packages/ludic.ui/attr_edge.ludic index d4a324fb..97adbd08 100644 --- a/packages/ludic.ui/attr_edge.ludic +++ b/packages/ludic.ui/attr_edge.ludic @@ -1,14 +1,14 @@ # attr_edge.ludic - border-radius, outline (and its width, colour and offset), scrollbar-color -function el_edge(n: UiNode, key: string, v: Val) -> bool { +function el_edge(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { let s = vl_text(v) - if key == "border-radius" { n.radius = el_len(v) } else if key == "outline" { el_outline(n, s) } else if key == "outline-width" { n.outline_w = el_len(v) } else if key == "outline-offset" { n.outline_off = el_len(v) } else if key == "outline-color" { - n.outline_c = ui_color(s) - n.outline_a = cl_alpha + if key == "border-radius" { n.radius = el_len(ui_st, v) } else if key == "outline" { el_outline(ui_st, n, s) } else if key == "outline-width" { n.outline_w = el_len(ui_st, v) } else if key == "outline-offset" { n.outline_off = el_len(ui_st, v) } else if key == "outline-color" { + n.outline_c = ui_color(ui_st, s) + n.outline_a = ui_st.cl_alpha } else if key == "scrollbar-color" { let parts = lss_split(s, 32) - if len(parts) > 0 { n.bar_c = ui_color(parts[0]) } - if len(parts) > 1 { n.bar_track = ui_color(parts[1]) } - } else if key == "background-image" and gr_is(tpl_words(s)) { nx(n).grad = gr_parse(s) } else if key == "background-image" { n.bg_img = el_url(s) } else if key == "border-image" { el_border_image(n, s) } else if key == "box-shadow" { el_shadow(n, s) } else if key == "animation" { el_animation(n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false } + if len(parts) > 0 { n.bar_c = ui_color(ui_st, parts[0]) } + if len(parts) > 1 { n.bar_track = ui_color(ui_st, parts[1]) } + } else if key == "background-image" and gr_is(tpl_words(s)) { nx(n).grad = gr_parse(ui_st, s) } else if key == "background-image" { n.bg_img = el_url(s) } else if key == "border-image" { el_border_image(ui_st, n, s) } else if key == "box-shadow" { el_shadow(ui_st, n, s) } else if key == "animation" { el_animation(n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false } return true } # url(path) or url("path") -> path @@ -20,17 +20,17 @@ function el_url(s: string) -> string { return t } # border-image: url(chip.png) 14 / 12px - the source's corner size, then how wide they are drawn -function el_border_image(n: UiNode, s: string) -> void { +function el_border_image(ui_st: UiState, n: UiNode, s: string) -> void { var close = 0 while close < len(s) and s[close] != 41 { close += 1 } n.bi_src = el_url(s[0..Math.min(close + 1, len(s))]) let rest = lss_split(s[Math.min(close + 1, len(s))..len(s)], 47) n.bi_slice = el_num(tpl_words(rest[0])) - n.bi_width = el_px(n.bi_slice) - if len(rest) > 1 { n.bi_width = el_unit(tpl_words(rest[1])) } + n.bi_width = el_px(ui_st, n.bi_slice) + if len(rest) > 1 { n.bi_width = el_unit(ui_st, tpl_words(rest[1])) } } # box-shadow: 6px 10px 0 rgba(0, 0, 0, 0.45) - x, y, a blur this renderer draws sharp, the colour -function el_shadow(n: UiNode, s: string) -> void { +function el_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void { if tpl_words(s) == "none" { n.sh_c = -2 return @@ -42,24 +42,24 @@ function el_shadow(n: UiNode, s: string) -> void { let p = parts[i] if len(p) == 0 { continue } if col == "" and ((p[0] >= 48 and p[0] <= 57) or p[0] == 45 or p[0] == 46) { - if nums == 0 { n.sh_x = el_unit(p) } - if nums == 1 { n.sh_y = el_unit(p) } + if nums == 0 { n.sh_x = el_unit(ui_st, p) } + if nums == 1 { n.sh_y = el_unit(ui_st, p) } nums += 1 } else if col == "" { col = p } else { col = col + " " + p } } if col == "" { col = "rgba(0,0,0,0.5)" } - n.sh_c = ui_color(col) - n.sh_a = cl_alpha + n.sh_c = ui_color(ui_st, col) + n.sh_a = ui_st.cl_alpha } # outline: 2px solid #ffcc00, or none -function el_outline(n: UiNode, s: string) -> void { +function el_outline(ui_st: mut UiState, n: UiNode, s: string) -> void { let parts = lss_split(s, 32) n.outline_w = 0.0 for i in 0 .. len(parts) { let p = parts[i] - if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { n.outline_w = el_unit(p) } else if len(p) > 0 and p != "solid" and p != "none" { - n.outline_c = ui_color(p) - n.outline_a = cl_alpha + if len(p) > 0 and ((p[0] >= 48 and p[0] <= 57) or p[0] == 46) { n.outline_w = el_unit(ui_st, p) } else if len(p) > 0 and p != "solid" and p != "none" { + n.outline_c = ui_color(ui_st, p) + n.outline_a = ui_st.cl_alpha } } } diff --git a/packages/ludic.ui/attr_fit.ludic b/packages/ludic.ui/attr_fit.ludic index cde3db3d..fa083941 100644 --- a/packages/ludic.ui/attr_fit.ludic +++ b/packages/ludic.ui/attr_fit.ludic @@ -1,6 +1,6 @@ # attr_fit.ludic - aspect-ratio (16 / 9, or 1.5), object-fit (fill, contain, cover, none, scale-down) # for pictures and natives, zoom (zoom.ludic), and a list split at its top-level commas -function el_fit_props(n: UiNode, key: string, s: string) -> bool { +function el_fit_props(ui_st: mut UiState, n: UiNode, key: string, s: string) -> bool { if key == "aspect-ratio" { let parts = lss_split(s, 47) var r = el_num(tpl_words(parts[0])) @@ -12,7 +12,7 @@ function el_fit_props(n: UiNode, key: string, s: string) -> bool { var k = 0 if w == "contain" { k = 1 } else if w == "cover" { k = 2 } else if w == "none" { k = 3 } else if w == "scale-down" { k = 4 } nx(n).fit_obj = k - } else if key == "zoom" { el_zoom(n, s) } else { return false } + } else if key == "zoom" { el_zoom(ui_st, n, s) } else { return false } return true } # "a, rgba(1, 2, 3, 0.5) 40%, b" -> its three parts diff --git a/packages/ludic.ui/attr_inset.ludic b/packages/ludic.ui/attr_inset.ludic index 7ae1430b..b33af5a1 100644 --- a/packages/ludic.ui/attr_inset.ludic +++ b/packages/ludic.ui/attr_inset.ludic @@ -1,21 +1,21 @@ # attr_inset.ludic - top, right, bottom and left as lengths, percentages or calc() of both: the # percentage is kept apart and taken of the containing block's width (left, right) or height (top, # bottom) when the element is placed -function el_inset(n: UiNode, side: int, v: Val) -> float { +function el_inset(ui_st: mut UiState, n: UiNode, side: int, v: Val) -> float { let s = vl_text(v) var pct = 0.0 var px = UI_AUTO if s != "auto" { if ca_is(s) { px = 0.0 - if ca_eval(s) { - pct = ca_pct - px = ca_px + if ca_eval(ui_st, s) { + pct = ui_st.ca_pct + px = ui_st.ca_px } } else if len(s) > 1 and s[len(s) - 1] == 37 { pct = el_num(s) px = 0.0 - } else { px = el_len(v) } + } else { px = el_len(ui_st, v) } } if pct != 0.0 or n.ex != null { let x = nx(n) diff --git a/packages/ludic.ui/attr_more.ludic b/packages/ludic.ui/attr_more.ludic index dbd4d887..426b78b0 100644 --- a/packages/ludic.ui/attr_more.ludic +++ b/packages/ludic.ui/attr_more.ludic @@ -1,26 +1,26 @@ # attr_more.ludic - width and height (where 0 is 0 and calc() may hold a percentage), the flex # shorthand, flex-shrink, order, text-shadow, pointer-events, text-fit and line-height (text_fit.ludic) -function el_more(n: UiNode, key: string, v: Val) -> bool { +function el_more(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { let s = vl_text(v) - if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else if key == "text-fit" { el_text_fit(n, s) } else if key == "line-height" { el_line_h(n, s) } else { return el_fit_props(n, key, s) } + if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(ui_st, n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else if key == "text-fit" { el_text_fit(ui_st, n, s) } else if key == "line-height" { el_line_h(ui_st, n, s) } else { return el_fit_props(ui_st, n, key, s) } return true } # width or height: a length (0 included), fill, a percentage, calc(), or auto/fit (none) -function el_dim(n: UiNode, wide: bool, v: Val) -> void { +function el_dim(ui_st: mut UiState, n: UiNode, wide: bool, v: Val) -> void { let s = vl_text(v) var fixed = UI_AUTO var pct = 0.0 var off = 0.0 if ca_is(s) { - if ca_eval(s) { - pct = ca_pct - off = ca_px - if pct <= 0.0 { fixed = Math.max(ca_px, 0.0) } + if ca_eval(ui_st, s) { + pct = ui_st.ca_pct + off = ui_st.ca_px + if pct <= 0.0 { fixed = Math.max(ui_st.ca_px, 0.0) } } } else if s == "fill" { fixed = UI_FILL } else if len(s) > 1 and s[len(s) - 1] == 37 { pct = el_num(s) if pct <= 0.0 { fixed = 0.0 } - } else if s != "auto" and s != "fit" and s != "" { fixed = el_len(v) } + } else if s != "auto" and s != "fit" and s != "" { fixed = el_len(ui_st, v) } if pct <= 0.0 { off = 0.0 } if n.ex != null or off != 0.0 { if wide { nx(n).w_off = off } else { nx(n).h_off = off } @@ -34,10 +34,10 @@ function el_dim(n: UiNode, wide: bool, v: Val) -> void { } } # a length out of calc(); a percentage has nothing to be a share of here -function el_calc_len(s: string) -> float { - if not ca_eval(s) { return 0.0 } - if ca_pct != 0.0 { ui_err(`{el_key}: {s} - a percentage in calc() is for a width or a height`) } - return ca_px +function el_calc_len(ui_st: mut UiState, s: string) -> float { + if not ca_eval(ui_st, s) { return 0.0 } + if ui_st.ca_pct != 0.0 { ui_err(ui_st, `{ui_st.el_key}: {s} - a percentage in calc() is for a width or a height`) } + return ui_st.ca_px } # flex: 1 | flex: 1 0 | flex: 2 1 auto | flex: none - the grow, then the shrink function el_flex_short(n: UiNode, s: string) -> void { @@ -67,7 +67,7 @@ function el_words(s: string) -> []string { return out } # text-shadow: 1px 2px 3px rgba(0, 0, 0, 0.6) - x, y, a blur drawn sharp, the colour; or none -function el_text_shadow(n: UiNode, s: string) -> void { +function el_text_shadow(ui_st: mut UiState, n: UiNode, s: string) -> void { let x = nx(n) x.tsh_set = true x.tsh_c = -2 @@ -78,17 +78,17 @@ function el_text_shadow(n: UiNode, s: string) -> void { for i in 0 .. len(parts) { let p = parts[i] if col == "" and ((p[0] >= 48 and p[0] <= 57) or p[0] == 45 or p[0] == 46) { - if nums == 0 { x.tsh_x = el_unit(p) } - if nums == 1 { x.tsh_y = el_unit(p) } + if nums == 0 { x.tsh_x = el_unit(ui_st, p) } + if nums == 1 { x.tsh_y = el_unit(ui_st, p) } nums += 1 } else if col == "" { col = p } else { col = col + " " + p } } if col == "" { col = "rgba(0,0,0,0.6)" } - x.tsh_c = ui_color(col) - x.tsh_a = cl_alpha + x.tsh_c = ui_color(ui_st, col) + x.tsh_a = ui_st.cl_alpha } # one word of a list of lengths (padding: calc(1em / 4) 2px) -function el_word_len(w: string) -> float { - if ca_is(w) { return el_calc_len(w) } - return el_unit(w) +function el_word_len(ui_st: mut UiState, w: string) -> float { + if ca_is(w) { return el_calc_len(ui_st, w) } + return el_unit(ui_st, w) } diff --git a/packages/ludic.ui/attr_place.ludic b/packages/ludic.ui/attr_place.ludic index 16ee6d40..31263c8e 100644 --- a/packages/ludic.ui/attr_place.ludic +++ b/packages/ludic.ui/attr_place.ludic @@ -1,17 +1,17 @@ # attr_place.ludic - position and its insets, z-index, overflow, white-space and text-overflow -function el_place_prop(n: UiNode, key: string, v: Val) -> bool { +function el_place_prop(ui_st: mut UiState, n: UiNode, key: string, v: Val) -> bool { let s = vl_text(v) if key == "position" { n.pos = 0 if s == "relative" { n.pos = 1 } if s == "absolute" { n.pos = 2 } if s == "fixed" { n.pos = 3 } - } else if key == "top" { n.top = el_inset(n, 0, v) } else if key == "right" { n.right = el_inset(n, 1, v) } else if key == "bottom" { n.bottom = el_inset(n, 2, v) } else if key == "left" { n.left = el_inset(n, 3, v) } else if key == "inset" { - el_sides(v) - n.top = el_s0 - n.right = el_s1 - n.bottom = el_s2 - n.left = el_s3 + } else if key == "top" { n.top = el_inset(ui_st, n, 0, v) } else if key == "right" { n.right = el_inset(ui_st, n, 1, v) } else if key == "bottom" { n.bottom = el_inset(ui_st, n, 2, v) } else if key == "left" { n.left = el_inset(ui_st, n, 3, v) } else if key == "inset" { + el_sides(ui_st, v) + n.top = ui_st.el_s0 + n.right = ui_st.el_s1 + n.bottom = ui_st.el_s2 + n.left = ui_st.el_s3 } else if key == "z-index" { n.z = int(el_num(s)) } else if key == "overflow" or key == "overflow-y" { if (s == "auto" or s == "scroll" or s == "hidden") and n.kind == UI_BOX { n.kind = UI_SCROLL } } else if key == "white-space" { n.nowrap = s == "nowrap" or s == "pre" } else if key == "text-overflow" { n.ellipsis = s == "ellipsis" } else { return false } diff --git a/packages/ludic.ui/backend.ludic b/packages/ludic.ui/backend.ludic index 447df199..21b2f3e2 100644 --- a/packages/ludic.ui/backend.ludic +++ b/packages/ludic.ui/backend.ludic @@ -30,30 +30,155 @@ function ui_default_backend() -> UiBackend { if len(UiRenderers) == 0 { return null } return UiRenderers[0].make() } -var ui_be: UiBackend = ui_default_backend() -export function ui_backend(b: UiBackend) -> void { ui_be = b } -function ui_px(v: float) -> float { - if ui_be != null and ui_be.scale != null { return v * ui_be.scale() } +export state UiState { + an_frames: []UiKeyframes = new []UiKeyframes + an_keys: []string = new []string + an_start: []float = new []float + an_frame: int = 0 + el_s0: float = 0.0 + el_s1: float = 0.0 + el_s2: float = 0.0 + el_s3: float = 0.0 + ui_be: UiBackend = ui_default_backend() + tr_no: bool = false # inside translate="no" while a frame is built + ca_s: string = "" + ca_i: int = 0 + ca_bad: string = "" + ca_px: float = 0.0 # the result: its lengths, its percentage, and whether it had units + ca_pct: float = 0.0 + ca_num: float = 0.0 # a plain number's own value, and whether it was one + ca_bare: bool = false + cs_pass: int = 0 + ua_sh: UiSheet = null + uc_list: []UiClass = null # filled by the compiler's registrations, before the program's globals + cl_alpha: float = 1.0 + ct_capture: string = "" + ct_capture_at: int = -1 # the frame listening began, whose own key started it + ct_capture_seen: int = -1 # the last frame that began listening, for ui_capturing + ct_num_key: string = "" + ct_num_text: string = "" + dv_on: bool = false + dv_frames: int = 0 + dv_dumped: bool = false + fr_ga: float = 1.0 # the opacity of everything around this element, which it multiplies + gr_a: float = 1.0 + fr_clips: []float = new []float + ui_view: UiView = null + ui_errs: []string = new []string + ui_at: string = "" + fc_key: string = "" + fc_visible: bool = false # focus came from the keyboard: :focus-visible, and the ring + fc_list: []UiNode = new []UiNode + fb_roots: []UiNode = new []UiNode + hd_key: string = "" + hd_t0: float = 0.0 + hd_last: float = 0.0 + in_now: UiInput = new UiInput + in_was_down: bool = false + in_last: UiInput = new UiInput + in_given: bool = false + rp_next: []float = [-1.0, -1.0, -1.0, -1.0] + rp_a: bool = false + rp_b: bool = false + rp_act: bool = false + in_act_down: bool = false # A or Enter went down this frame, or came up + in_act_up: bool = false + in_list: []UiInst = new []UiInst + in_build: int = 0 + ly_fg_a: float = 1.0 + ly_lead: float = 0.0 + ly_extra: float = 0.0 + lib_files: []UiFile = new []UiFile + lib_screen_names: []string = new []string + lib_screens: []UiTpl = new []UiTpl + lc_keys: []string = new []string + lc_mounts: []UiAct = new []UiAct + lc_unmounts: []UiAct = new []UiAct + lc_envs: []UiEnv = new []UiEnv + lc_prev_keys: []string = new []string + lc_prev_unmounts: []UiAct = new []UiAct + lc_prev_envs: []UiEnv = new []UiEnv + nt_list: []UiNative = new []UiNative + nt_q_nodes: []UiNode = new []UiNode + nt_q_names: []string = new []string + nt_q_vals: []Val = new []Val + fr_hover: []string = new []string + fr_press_key: string = "" + fr_down_now: bool = false + fr_key_press: string = "" # the control Enter or the pad's A is holding down + pe_key: string = "" # the element the pointer is captured by + pe_btn: int = 0 + pop_list: []UiNode = new []UiNode + pp_bx: float = 0.0 + pp_by: float = 0.0 + pp_bw: float = 0.0 + pp_bh: float = 0.0 + r3u_user: float = 1.0 + r3u_tex_paths: []string = new []string + r3u_tex_ids: []int = new []int + r3u_tex_sizes: []int = new []int + r3u_tex_seen: []float = new []float + r3u_atlas: []string = new []string + r3u_atlas_tex: []int = new []int + r3u_atlas_cols: []int = new []int + r3u_atlas_rows: []int = new []int + r3u_atlas_names: [][]string = new [][]string + sc_keys: []string = new []string + sc_offs: []float = new []float + sc_drag: string = "" + sc_scope: UiNode = null # the popover that has the pointer this frame, if one is up + sb_x: float = 0.0 + sb_w: float = 0.0 + sb_ty: float = 0.0 + sb_th: float = 0.0 + sb_bar: UiNode = null + sc_grab: float = 0.0 # how far below the thumb's top the pointer holds it + sc_want_keys: []string = new []string + sc_want_px: []float = new []float + st_keys: []string = new []string + st_vals: []Val = new []Val + tf_size: float = 0.0 # the size tf_fit drew at + tt_key: string = "" + tt_since: float = 0.0 + tt_text: string = "" + tt_x: float = 0.0 + tt_y: float = 0.0 + tt_by_keys: bool = false # the focus moved by the keyboard since the pointer last moved + tt_focus_seen: string = "" + tp_nl: []int = new []int + tr_keys: []string = new []string + tr_from: []float = new []float + tr_to: []float = new []float + tr_at: []float = new []float + el_cur: UiNode = null + el_key: string = "" + ly_vp_x: float = 0.0 + ly_vp_y: float = 0.0 + ly_vp_w: float = 0.0 + ly_vp_h: float = 0.0 +} +export function ui_backend(ui_st: mut UiState, b: UiBackend) -> void { ui_st.ui_be = b } +function ui_px(ui_st: UiState, v: float) -> float { + if ui_st.ui_be != null and ui_st.ui_be.scale != null { return v * ui_st.ui_be.scale() } return v } -var tr_no: bool = false # inside translate="no" while a frame is built -function ui_tr(s: string) -> string { - if tr_no { return s } - if ui_be != null and ui_be.translate != null { return ui_be.translate(s) } +function ui_tr(ui_st: UiState, s: string) -> string { + if ui_st.tr_no { return s } + if ui_st.ui_be != null and ui_st.ui_be.translate != null { return ui_st.ui_be.translate(s) } return s } -function ui_sound(name: string) -> void { - if ui_be != null and ui_be.sound != null { ui_be.sound(name) } +function ui_sound(ui_st: UiState, name: string) -> void { + if ui_st.ui_be != null and ui_st.ui_be.sound != null { ui_st.ui_be.sound(name) } } # the program's language: every text a template shows goes through `f` before it is measured -export function ui_translator(f: fn(string) -> string) -> void { - if ui_be != null { ui_be.translate = f } +export function ui_translator(ui_st: mut UiState, f: fn(string) -> string) -> void { + if ui_st.ui_be != null { ui_st.ui_be.translate = f } } # the program's sounds: "click" when a control is used, or the control's own sound="..." -export function ui_sounds(f: fn(string) -> void) -> void { - if ui_be != null { ui_be.sound = f } +export function ui_sounds(ui_st: mut UiState, f: fn(string) -> void) -> void { + if ui_st.ui_be != null { ui_st.ui_be.sound = f } } # the program's clock, in seconds, for animation (a game's own time, which a headless run counts) -export function ui_clock(f: fn() -> float) -> void { - if ui_be != null { ui_be.now = f } +export function ui_clock(ui_st: mut UiState, f: fn() -> float) -> void { + if ui_st.ui_be != null { ui_st.ui_be.now = f } } diff --git a/packages/ludic.ui/build.ludic b/packages/ludic.ui/build.ludic index 425de853..74b12d07 100644 --- a/packages/ludic.ui/build.ludic +++ b/packages/ludic.ui/build.ludic @@ -2,47 +2,47 @@ # are the template's own; a tag its file linked to a component is that one, # given its attributes as props (over the defaults its own tag gives: ), its on-* as what `emit` runs, and its content as . -function bd_kids(kids: []UiTpl, e0: UiEnv, key: string, out: []UiNode) -> void { +function bd_kids(ui_st: mut UiState, kids: []UiTpl, e0: UiEnv, key: string, out: []UiNode) -> void { var last_if = true var e = e0 for i in 0 .. len(kids) { let k = kids[i] - ui_at = k.where + ui_st.ui_at = k.where let ck = `{key}/{i}` if k.tag == "state" or k.tag == "style" { continue } if k.tag == "let" { - e = bd_let(k, e) + e = bd_let(ui_st, k, e) continue } - if k.tag == "fragment" or k.tag == "#text" or (k.tag == "template" and tpl_attr(k, "slot") == null) { bd_group(k, e, ck, out) } else if k.tag == "provide" { bd_provide(k, e, ck, out) } else if k.tag == "template" { continue } else if k.tag == "if" { - last_if = bd_test(k, e) - if last_if { bd_kids(k.kids, e, ck, out) } + if k.tag == "fragment" or k.tag == "#text" or (k.tag == "template" and tpl_attr(k, "slot") == null) { bd_group(ui_st, k, e, ck, out) } else if k.tag == "provide" { bd_provide(ui_st, k, e, ck, out) } else if k.tag == "template" { continue } else if k.tag == "if" { + last_if = bd_test(ui_st, k, e) + if last_if { bd_kids(ui_st, k.kids, e, ck, out) } } else if k.tag == "else" { - if not last_if { bd_kids(k.kids, e, ck, out) } + if not last_if { bd_kids(ui_st, k.kids, e, ck, out) } last_if = true - } else if k.tag == "each" { bd_each(k, e, ck, out) } else if k.tag == "slot" { bd_slot(k, e, ck, out) } else if not bd_comp(k, e, ck, out) { push(out, bd_elem(k, e, ck)) } + } else if k.tag == "each" { bd_each(ui_st, k, e, ck, out) } else if k.tag == "slot" { bd_slot(ui_st, k, e, ck, out) } else if not bd_comp(ui_st, k, e, ck, out) { push(out, bd_elem(ui_st, k, e, ck)) } } } -function bd_test(k: UiTpl, e: UiEnv) -> bool { +function bd_test(ui_st: mut UiState, k: UiTpl, e: UiEnv) -> bool { let t = tpl_attr(k, "test") if t == null { - ui_err(" needs a test=\"{...}\"") + ui_err(ui_st, " needs a test=\"{...}\"") return false } - return ui_eval_bool(t, e) + return ui_eval_bool(ui_st, t, e) } function bd_name(k: UiTpl, attr: string, dflt: string) -> string { let x = tpl_attr(k, attr) if x == null or x.op != X_LIT { return dflt } return Value.as_str(x.v) } -function bd_each(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void { +function bd_each(ui_st: mut UiState, k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void { let src = tpl_attr(k, "in") if src == null { - ui_err(" needs an in=\"{...}\"") + ui_err(ui_st, " needs an in=\"{...}\"") return } - let list = ui_eval(src, e) + let list = ui_eval(ui_st, src, e) let as_name = bd_name(k, "as", "item") let at_name = bd_name(k, "index", "index") let by = tpl_attr(k, "key") @@ -51,28 +51,28 @@ function bd_each(k: UiTpl, e: UiEnv, key: string, out: []UiNode) -> void { env_bind(le, as_name, Value.at(list, j)) env_bind(le, at_name, Value.int(j)) var id = string(j) - if by != null { id = ui_eval_text(by, le) } - bd_kids(k.kids, le, `{key}#{id}`, out) + if by != null { id = ui_eval_text(ui_st, by, le) } + bd_kids(ui_st, k.kids, le, `{key}#{id}`, out) } } # is what the component's user put inside it; their