fix(ui): 0.R4 - an action a UI button dispatches is reduced before the frame is presented
ludic.ui runs a frame's presses after drawing it, and what they dispatched waited for the end of the phase - after the host had presented - so a button's change showed a frame late. Decided: drain, not a phase per action. ui_show and ui_press drain the queue once the presses have run, so the code after them and the frame presented next see the change; a host that runs presses some other way calls drain_actions() before it presents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d46f0adb5e
commit
259b4d9b35
7 changed files with 46 additions and 3 deletions
|
|
@ -371,8 +371,10 @@ program Pack {
|
|||
dispatching needs no state parameter.
|
||||
- **When the queue is drained:** at the end of every phase of the frame loop (so what the `Input`
|
||||
phase dispatches is reduced before `Update`); after every phase of the ludic.base system runner
|
||||
(`core_tick_all`); and wherever the program calls `drain_actions()` (an `entry` program, a test,
|
||||
a loop of its own). Draining runs the actions in the order they were dispatched, and each action's
|
||||
(`core_tick_all`); when ludic.ui has run a frame's presses (`ui_show`, `ui_press`), so a button's
|
||||
action is reduced before the host presents the frame, not a frame later; and wherever the program
|
||||
calls `drain_actions()` (an `entry` program, a test, a loop of its own - and a host that runs UI
|
||||
presses some other way calls it before it presents). Draining runs the actions in the order they were dispatched, and each action's
|
||||
reducers in the order of their states' names - never the order of imports - so the same actions
|
||||
make the same changes on every machine and in a replay.
|
||||
- **An action a reducer dispatches** is queued behind the rest and reduced in the same drain, never
|
||||
|
|
|
|||
7
changes/ui-press-drain.md
Normal file
7
changes/ui-press-drain.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**An action a UI button dispatches is reduced before the frame is presented.** ludic.ui runs a frame's
|
||||
presses after drawing it, and an action they dispatched waited for the end of the phase - after the
|
||||
host had presented - so what the button changed showed a frame late. `ui_show` and `ui_press` now
|
||||
drain the queue once the presses have run. A host that runs presses some other way calls
|
||||
`drain_actions()` before it presents.
|
||||
22
examples/actions/ui_press.ludic
Normal file
22
examples/actions/ui_press.ludic
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# ui_press.ludic - 0.R4: an action a UI control dispatches is reduced before the UI hands control
|
||||
# back, so the code after it - and the frame the host presents - already sees the change
|
||||
import "ludic.ui"
|
||||
program UiPress {
|
||||
numbers float
|
||||
state Score { points: int = 0 }
|
||||
action Scored { n: int = 1 }
|
||||
reducer Score on Scored(s: mut Score, a: Scored) { s.points += a.n }
|
||||
import "ui_press_parts/Adder.ludic"
|
||||
function frame(ui_st: mut UiState) -> UiNode {
|
||||
let root: UiNode = ui_nodes(ui_st, "Adder", null)
|
||||
ui_place(ui_st, root, 0.0, 0.0, 300.0, 200.0)
|
||||
return root
|
||||
}
|
||||
entry (ui_st: mut UiState, score: Score) {
|
||||
let root = frame(ui_st)
|
||||
ui_press(ui_st, root.children[0].children[0])
|
||||
let after_one = score.points
|
||||
ui_press(ui_st, root.children[0].children[0])
|
||||
print(`{after_one} {score.points}`)
|
||||
}
|
||||
}
|
||||
4
examples/actions/ui_press_parts/Adder.ludic
Normal file
4
examples/actions/ui_press_parts/Adder.ludic
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Adder.ludic - a button that says what happened and changes nothing itself
|
||||
component Adder {
|
||||
on add(n: int) { dispatch Scored { n: n } }
|
||||
}
|
||||
3
examples/actions/ui_press_parts/Adder.xml
Normal file
3
examples/actions/ui_press_parts/Adder.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<div>
|
||||
<button on-click="add(5)">+5</button>
|
||||
</div>
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
# frame.ludic - a screen shown: built from the view's model, laid out, drawn, and its presses
|
||||
# run once the frame is drawn, so nothing the view does can change a frame half way through it
|
||||
# run once the frame is drawn, so nothing the view does can change a frame half way through it;
|
||||
# the actions they dispatch are reduced before ui_show returns, so the frame presented next is built
|
||||
# from what they changed
|
||||
|
||||
export function ui_show(ui_st: mut UiState, screen: string, view: UiView, x: float, y: float, w: float, h: float) -> void {
|
||||
in_read(ui_st)
|
||||
|
|
@ -25,6 +27,7 @@ export function ui_show(ui_st: mut UiState, screen: string, view: UiView, x: flo
|
|||
dv_after(ui_st, screen, root)
|
||||
lc_run(ui_st, screen)
|
||||
nt_fired_run(ui_st)
|
||||
drain_actions() # 0.R4: what the presses dispatched is reduced before the host presents
|
||||
}
|
||||
# the screen's nodes for this frame, not yet laid out - for a test, or a host that places them
|
||||
export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> UiNode {
|
||||
|
|
@ -78,6 +81,7 @@ export function ui_place(ui_st: mut UiState, root: UiNode, x: float, y: float, w
|
|||
# a press, as if the pointer had made it
|
||||
export function ui_press(ui_st: mut UiState, n: UiNode) -> void {
|
||||
if n.act != null and n.enabled { act_run(ui_st, n.act, n.env) }
|
||||
drain_actions()
|
||||
}
|
||||
# any event, as if the element had fired it - for a test
|
||||
export function ui_event(ui_st: mut UiState, n: UiNode, name: string, value: Val) -> void { nt_run(ui_st, n, name, value) }
|
||||
|
|
|
|||
|
|
@ -1012,6 +1012,7 @@ function cmd_dev_test() -> int {
|
|||
reject_case("rejected/layer_reach", "hud uses items.item_count", "a layered module is still held to its uses outside the layer")
|
||||
reject_case("rejected/layer_cycle", "go round in a circle: items -> layer app -> items", "a cycle through a layer and out of it is refused")
|
||||
feat_case("actions/pack", "", "0 1 9 13 picked 7 picked 9 too heavy", "pack.ludic (0.R: actions, reducers - one per state, in the order of the states' names - dispatch, drain_actions, an action a reducer dispatches goes behind)")
|
||||
controller_case("actions/ui_press", "", "5 10", "ui_press.ludic (0.R4: an action a UI press dispatches is reduced before ludic.ui hands control back)")
|
||||
feat_case("actions/modules", "", "2", "modules.ludic (0.R: an action in one module, its reducer in another)")
|
||||
actions_deps_case()
|
||||
deps_reach_case()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue