Skip to content

Commit 36995a9

Browse files
authored
Merge pull request #28 from talex5/fix-staircasing
Fix staircasing in layout
2 parents d0a3247 + c91623d commit 36995a9

File tree

5 files changed

+152
-56
lines changed

5 files changed

+152
-56
lines changed

examples/limiter/dune

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(executable
2+
(name main)
3+
(libraries eio.mock))

examples/limiter/main.ml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(* This runs jobs 4 at a time. It's useful to check eio-trace's layout algorithm. *)
2+
3+
open Eio.Std
4+
5+
let lock = Eio.Mutex.create ()
6+
7+
let main () =
8+
List.init 15 Fun.id
9+
|> Fiber.List.iter ~max_fibers:4 (fun i ->
10+
if i = 4 then Fiber.both Fiber.yield Fiber.yield;
11+
Eio.Mutex.use_ro lock Fiber.yield)
12+
13+
let () =
14+
Eio_mock.Backend.run main

lib/layout.ml

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -104,73 +104,88 @@ let as_string = function
104104
| `String s -> s
105105
| _ -> failwith "Not a string"
106106

107-
let layout ~duration (ring : Ring.t) =
107+
(* Walk the tree rooted at [i], calculating heights and positions.
108+
Note: visited items have their [y] fields set to their position relative to
109+
their parent. This gets turned into an absolute position later. *)
110+
let rec layout_item ~duration (i : item) =
111+
i.y <- 0;
112+
i.height <- 1;
113+
i.end_cc_label <- None;
114+
let intervals = ref [] in
115+
(* Position CCs at the same place as [i]. *)
116+
i.events |> Array.iter (fun (ts, e) ->
117+
match e with
118+
| Log _ | Error _ ->
119+
if i.end_cc_label = None then (
120+
i.end_cc_label <- Some ts;
121+
)
122+
| Add_fiber _ -> ()
123+
| Create_cc (_, child) ->
124+
if debug_layout then Fmt.epr "%d creates cc %d (%a)@." i.id child.id Fmt.(option string) child.name;
125+
if i.end_cc_label = None then (
126+
i.end_cc_label <- Some ts;
127+
);
128+
layout_item ~duration child;
129+
i.height <- max i.height child.height;
130+
let stop = Option.value child.end_time ~default:duration in
131+
intervals := { Itv.value = child; start = ts; stop } :: !intervals;
132+
);
133+
if i.end_cc_label = None then i.end_cc_label <- i.end_time;
134+
let n_ccs = List.length !intervals in
135+
(* Position fibers where there is space, not overlapping CCs or other fibers. *)
136+
i.events |> Array.iter (fun (ts, e) ->
137+
match e with
138+
| Log _ | Error _ | Create_cc _ -> ()
139+
| Add_fiber { parent; child } ->
140+
if debug_layout then Fmt.epr "%d gets fiber %d, created by %d@." i.id child.id parent;
141+
let stop = Option.value child.end_time ~default:duration in
142+
intervals := { Itv.value = child; start = ts; stop } :: !intervals;
143+
);
144+
let intervals = List.rev !intervals in
145+
let itv = Itv.create intervals in
146+
let height = ref i.height in
147+
intervals |> List.to_seq |> Seq.drop n_ccs |> Seq.iter (fun (interval : _ Itv.interval) ->
148+
let space = Space.create 1 in
149+
let adjust other = Space.mark_range space other.y (other.y + other.height) in
150+
Itv.iter_overlaps adjust interval.start interval.stop itv;
151+
let f = interval.Itv.value in
152+
layout_item ~duration f;
153+
f.y <- Space.first_free space f.height;
154+
height := max !height (f.y + f.height);
155+
);
156+
i.height <- !height
157+
158+
(* Walk the tree rooted at [i] converting relative [y] values to absolute ones,
159+
using [parent] for zero. *)
160+
let rec make_y_absolute i parent =
161+
i.y <- i.y + parent;
162+
if debug_layout then Fmt.epr "%d is at %d (height %d)@." i.id i.y i.height;
163+
i.events |> Array.iter (fun (_ts, e) ->
164+
match e with
165+
| Log _ | Error _ -> ()
166+
| Add_fiber { parent = _; child }
167+
| Create_cc (_, child) -> make_y_absolute child i.y
168+
)
169+
170+
let layout_ring ~duration (ring : Ring.t) =
108171
let max_y = ref 1 in
109-
let rec visit ~y (i : item) =
110-
if debug_layout then Fmt.epr "%d is at %d@." i.id y;
111-
i.y <- y;
112-
i.height <- 1;
113-
i.end_cc_label <- None;
114-
let intervals = ref [] in
115-
i.events |> Array.iter (fun (ts, e) ->
116-
match e with
117-
| Log _ | Error _ ->
118-
if i.end_cc_label = None then (
119-
i.end_cc_label <- Some ts;
120-
)
121-
| Add_fiber _ -> ()
122-
| Create_cc (_, child) ->
123-
if debug_layout then Fmt.epr "%d creates cc %d (%a)@." i.id child.id Fmt.(option string) child.name;
124-
if i.end_cc_label = None then (
125-
i.end_cc_label <- Some ts;
126-
);
127-
visit ~y child;
128-
i.height <- max i.height child.height;
129-
let stop = Option.value child.end_time ~default:duration in
130-
intervals := { Itv.value = child; start = ts; stop } :: !intervals;
131-
);
132-
if i.end_cc_label = None then i.end_cc_label <- i.end_time;
133-
let start_fibers = List.length !intervals in
134-
i.events |> Array.iter (fun (ts, e) ->
135-
match e with
136-
| Log _ | Error _ | Create_cc _ -> ()
137-
| Add_fiber { parent; child } ->
138-
if debug_layout then Fmt.epr "%d gets fiber %d, created by %d@." i.id child.id parent;
139-
let stop = Option.value child.end_time ~default:duration in
140-
intervals := { Itv.value = child; start = ts; stop } :: !intervals;
141-
);
142-
let intervals = List.rev !intervals in
143-
let itv = Itv.create intervals in
144-
let height = ref i.height in
145-
intervals |> List.to_seq |> Seq.drop start_fibers |> Seq.iter (fun (interval : _ Itv.interval) ->
146-
let y = ref (i.y + 1) in
147-
let adjust other =
148-
y := max !y (other.y + other.height);
149-
in
150-
Itv.iter_overlaps adjust interval.start interval.stop itv;
151-
let f = interval.Itv.value in
152-
visit ~y:!y f;
153-
height := max !height (f.y - i.y + f.height);
154-
);
155-
i.height <- !height;
156-
max_y := max !max_y i.y;
157-
if debug_layout then Fmt.epr "%d is at %d+%d@." i.id y i.height;
158-
in
159172
let visit_domain root =
160173
root.Ring.cc |> Option.iter @@ fun (_ts, (i : item)) ->
161-
i.y <- ring.y + 1;
174+
i.y <- 1;
162175
i.height <- 1;
163176
i.end_cc_label <- None;
164177
i.events |> Array.iter (fun (_ts, e) ->
165178
match e with
166179
| Log _ | Error _ | Create_cc _ -> ()
167180
| Add_fiber { parent = _; child } ->
168-
visit ~y:(ring.y + i.height) child;
169-
i.height <- child.y - ring.y + child.height;
181+
layout_item ~duration child;
182+
i.height <- max i.height (child.y + child.height);
170183
if i.end_cc_label = None then (
171184
i.end_cc_label <- child.end_cc_label;
172185
);
173186
);
187+
make_y_absolute i ring.y;
188+
max_y := max !max_y (i.y + i.height - 1);
174189
in
175190
List.iter visit_domain ring.roots;
176191
ring.height <- (!max_y + 1) - ring.y
@@ -233,7 +248,7 @@ let of_trace (trace : Trace.t) =
233248
let y = ref 0 in
234249
rings |> Trace.Rings.iter (fun _ (ring : Ring.t) ->
235250
ring.y <- !y;
236-
layout ring ~duration;
251+
layout_ring ring ~duration;
237252
y := !y + ring.height;
238253
);
239254
let height = !y in

lib/space.ml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
(* Tracks which of a set of rows have free space.
2+
For large sets it might be more efficient to use an integer tree,
3+
but layouts that large are fairly unusable anyway. *)
4+
5+
type t = {
6+
start : int;
7+
mutable buf : bytes;
8+
}
9+
10+
let create start = { start; buf = Bytes.empty }
11+
12+
let mark t row =
13+
let i = row - t.start in
14+
if i >= 0 then (
15+
let byte = i lsr 3 in
16+
if byte >= Bytes.length t.buf then (
17+
let old_buf = t.buf in
18+
let old_len = Bytes.length old_buf in
19+
let new_len = max (byte + 1) (old_len * 2) in
20+
let new_buf = Bytes.extend old_buf 0 (new_len - old_len) in
21+
Bytes.fill new_buf old_len (new_len - old_len) (Char.chr 0);
22+
t.buf <- new_buf
23+
);
24+
let buf = t.buf in
25+
let v = Bytes.get_uint8 buf byte lor (1 lsl (i land 0x7)) in
26+
Bytes.set_uint8 buf byte v
27+
)
28+
29+
let mark_range t a b =
30+
let a = max a t.start in
31+
for i = a to b - 1 do
32+
mark t i
33+
done
34+
35+
let rec find_free_bit v start =
36+
if v land 1 = 0 then start
37+
else find_free_bit (v lsr 1) (start + 1)
38+
39+
let (.%[]) t row =
40+
let i = row - t.start in
41+
let buf = t.buf in
42+
let byte = i lsr 3 in
43+
if byte >= Bytes.length buf then false
44+
else (Bytes.get_uint8 buf byte land (1 lsl (i land 7))) <> 0
45+
46+
let first_free t len =
47+
assert (len >= 0);
48+
let rec check i need =
49+
if need = 0 then i - len
50+
else if t.%[i] then check (i + 1) len
51+
else check (i + 1) (need - 1)
52+
in
53+
check t.start len

test/test.ml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module Itv = Eio_trace.Itv
2+
module Space = Eio_trace.Space
23

34
let span = Crowbar.(map [uint8; uint8]) (fun start len -> (float start, float (start + len)))
45

@@ -24,5 +25,15 @@ let test_ivt spans (start, stop) =
2425
)
2526
)
2627

28+
let test_space start used height =
29+
let s = Space.create start in
30+
List.iter (Space.mark s) used;
31+
let free = Space.first_free s height in
32+
for i = free to free + height - 1 do
33+
if List.mem i used then
34+
Crowbar.failf "Row %d is used, but was returned as free (%d+%d)!" i free height
35+
done
36+
2737
let () =
28-
Crowbar.(add_test ~name:"ivt" [list span; span] test_ivt)
38+
Crowbar.(add_test ~name:"ivt" [list span; span] test_ivt);
39+
Crowbar.(add_test ~name:"space" [int8; list int8; uint8] test_space)

0 commit comments

Comments
 (0)