Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion platform/native/lui_paint/lui_retained.ml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type retained = {
mutable scale : float;
(* Any mark since the last materialize. *)
mutable dirty : bool;
mutable resync : bool;
(* Materialize generation — fragments record which frame last
pushed their buffers. *)
mutable mgen : int;
Expand All @@ -118,12 +119,16 @@ let create () =
{ frags = Hashtbl.create 256; fparent = Hashtbl.create 256;
pend_kids = Hashtbl.create 16; pend_segs = Hashtbl.create 64;
rev = -1; vp = rect 0. 0. 0. 0.; scale = 1.;
dirty = false; mgen = 0; stat_repaints = 0; stat_visits = 0;
dirty = false; resync = false; mgen = 0; stat_repaints = 0; stat_visits = 0;
vwin = Hashtbl.create 4; mounted = (fun _ -> None) }

let invalidate (rt : retained) =
Hashtbl.reset rt.frags;
Hashtbl.reset rt.vwin;
(* Fresh fragments start with no kid list and nothing marks them:
the next paint must re-derive every node's children, or the tree
paints as childless roots (an empty frame). *)
rt.resync <- true;
rt.dirty <- true

let stat_repaints (rt : retained) = rt.stat_repaints
Expand Down Expand Up @@ -699,6 +704,14 @@ let paint_retained rt hooks store (scene : Lui_scene.t) ?moved ?hints ?vlists
end;
if Lui_store.revision store <> rt.rev then advance rt store;
rt.mounted <- hooks.Lui_paint.mounted_children;
if rt.resync then begin
rt.resync <- false;
List.iter
(fun root ->
List.iter (fun id -> mark_rekids rt store id)
(Lui_store.preorder ~root store))
(Lui_store.root_ids store)
end;
(* A virtual list whose mounted window slid (scroll, resize) has a
stale kids list even though no store op named it. *)
(match vlists with
Expand Down
33 changes: 32 additions & 1 deletion platform/native/lui_paint/test/lui_retained_test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,35 @@ let test_random_equivalence () =
List.iter run_seed
[ [| 0x5EED; 0xBEEF |]; [| 1; 0xC0FFEE |]; [| 33; 7 |]; [| 104; 42 |] ]

(* A batch larger than the store journal (8192 ops) truncates the retained
cursor; the wholesale resync must re-derive every kid list, not
paint childless roots (an empty frame). *)
let test_journal_overflow_resync () =
let env = env () in
build_rows env 4;
frame env ();
let ops =
List.concat_map
(fun i ->
let id = 1000 + i in
[ CreateNode (id, Button);
SetProp (id, TextValue, StringValue "x");
InsertChild (1, id, 4 + i) ])
(List.init 5000 Fun.id)
in
apply env.store ops;
frame env ~height:720 ();
check bool "frame not empty" true (env.kept.ops <> [])

(* Same for an explicit wholesale invalidation (theme swap). *)
let test_invalidate_rebuilds_kids () =
let env = env () in
build_rows env 6;
frame env ();
Lui_retained.invalidate env.rt;
frame env ();
check bool "frame not empty" true (env.kept.ops <> [])

let () =
run "lui_retained"
[ ( "fragments",
Expand All @@ -549,4 +578,6 @@ let () =
test_case "drop subtree" `Quick test_drop_subtree;
test_case "wide removal" `Quick test_wide_removal;
test_case "vlist scroll" `Quick test_vlist_scroll;
test_case "random equivalence" `Quick test_random_equivalence ] ) ]
test_case "random equivalence" `Quick test_random_equivalence;
test_case "journal overflow resync" `Quick test_journal_overflow_resync;
test_case "invalidate rebuilds kids" `Quick test_invalidate_rebuilds_kids ] ) ]
Loading