From 98d7e75fca7587fcae741313a44f4069922e164e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 8 Oct 2026 22:22:29 -0400 Subject: [PATCH] Bench: edits timed at the drawn tile; per-cycle document counts; posted chords --- crates/ely-lok/src/view.rs | 10 ++-- crates/ely-lok/src/view/clock.rs | 51 +++++++++++++++---- crates/ely-lok/src/view/render.rs | 8 ++- tests/perf/__pycache__/bench.cpython-314.pyc | Bin 33352 -> 33950 bytes tests/perf/bench.py | 34 ++++++++----- tests/perf/test_bench.py | 6 +++ tests/scripts/chord.swift | 14 +++++ 7 files changed, 93 insertions(+), 30 deletions(-) create mode 100644 tests/scripts/chord.swift diff --git a/crates/ely-lok/src/view.rs b/crates/ely-lok/src/view.rs index 6da81c6..26073e4 100644 --- a/crates/ely-lok/src/view.rs +++ b/crates/ely-lok/src/view.rs @@ -471,15 +471,11 @@ impl LokView { log::warn!("lok: a tile came back the wrong size"); return; }; - let entry = TileEntry { - image: Arc::new(RenderImage::new([Frame::new(image)])), - stale: false, - }; - log::debug!("lok: tile {key:?} shown"); - if let Some(old) = self.tiles.insert(key, entry) { + let image = Arc::new(RenderImage::new([Frame::new(image)])); + self.clock.painted(tile.generation, Arc::as_ptr(&image) as usize); + if let Some(old) = self.tiles.insert(key, TileEntry { image, stale: false }) { self.retired.push(old.image); } - self.clock.painted(tile.generation); cx.notify(); } Ok(_) => cx.notify(), diff --git a/crates/ely-lok/src/view/clock.rs b/crates/ely-lok/src/view/clock.rs index 9da6bf7..2345ba9 100644 --- a/crates/ely-lok/src/view/clock.rs +++ b/crates/ely-lok/src/view/clock.rs @@ -7,6 +7,8 @@ type Edit = (Instant, Option, Option); #[derive(Default)] pub(super) struct EditClock { edit: Option, + /// The tile that shows the edit, until it is drawn. + awaited: Option, /// The last generation bump: its revision and generation. bumped: (u64, u64), } @@ -42,16 +44,30 @@ impl EditClock { } } - /// A tile of `generation` came; logs the edit it shows. - pub(super) fn painted(&mut self, generation: u64) { - if let Some((at, _, Some(shown))) = self.edit + /// A tile of `generation` came as `tile`; it awaits drawing. + pub(super) fn painted(&mut self, generation: u64, tile: usize) { + if let Some((_, _, Some(shown))) = self.edit && generation >= shown { - log::debug!("lok: edit to tile {} ms", at.elapsed().as_millis()); - self.edit = None; + self.awaited = Some(tile); } } + /// `tile` reached the window; the edit it shows is timed. + pub(super) fn drawn(&mut self, tile: usize) { + if self.awaited == Some(tile) + && let Some((at, ..)) = self.edit.take() + { + log::debug!("lok: edit to tile {} ms", at.elapsed().as_millis()); + self.awaited = None; + } + } + + /// Whether a drawn `tile` would end the timing. + pub(super) fn awaits(&self, tile: usize) -> bool { + self.awaited == Some(tile) + } + #[cfg(test)] fn running(&self) -> bool { self.edit.is_some() @@ -68,9 +84,12 @@ mod tests { clock.sent(); clock.bumped(7, 3); clock.landed(Some(7)); - clock.painted(2); + clock.painted(2, 20); + clock.drawn(20); assert!(clock.running(), "an older picture leaves it running"); - clock.painted(3); + clock.painted(3, 30); + assert!(clock.running(), "arriving is not yet drawn"); + clock.drawn(30); assert!(!clock.running(), "the edit's own picture ends it"); } @@ -80,10 +99,12 @@ mod tests { clock.sent(); clock.bumped(6, 3); clock.landed(Some(7)); - clock.painted(3); + clock.painted(3, 30); + clock.drawn(30); assert!(clock.running(), "a batch before the edit shows the old text"); clock.bumped(7, 4); - clock.painted(4); + clock.painted(4, 40); + clock.drawn(40); assert!(!clock.running()); } @@ -94,4 +115,16 @@ mod tests { clock.landed(None); assert!(!clock.running()); } + + #[test] + fn a_tile_that_never_draws_keeps_the_edit_open() { + let mut clock = EditClock::default(); + clock.sent(); + clock.bumped(5, 2); + clock.landed(Some(5)); + clock.painted(2, 50); + assert!(clock.awaits(50)); + clock.drawn(51); + assert!(clock.running(), "another tile's drawing does not count"); + } } diff --git a/crates/ely-lok/src/view/render.rs b/crates/ely-lok/src/view/render.rs index 81a1792..2f5d130 100644 --- a/crates/ely-lok/src/view/render.rs +++ b/crates/ely-lok/src/view/render.rs @@ -110,7 +110,13 @@ impl Render for LokView { ) }); match painted { - Ok(()) => drawn = true, + Ok(()) => { + drawn = true; + let tile = Arc::as_ptr(image) as usize; + if this.read(app).clock.awaits(tile) { + this.update(app, |view, _| view.clock.drawn(tile)); + } + } Err(error) => log::error!("lok: tile paint failed: {error}"), } } diff --git a/tests/perf/__pycache__/bench.cpython-314.pyc b/tests/perf/__pycache__/bench.cpython-314.pyc index 089db20b55dcce2355b5cc1283e36d83fb270c44..e11d7c70c2149ae6a3a971606fcc25a51ad5602a 100644 GIT binary patch delta 3549 zcmb7GYj9J?72ds)uAWy9{FGn1vaqmXu<;8M+dLfb1MJwcY|91&R9M2O2urz=0Xrr@ znwgR`nNYTAptK3Y6lw|-Q-vlalO~hN!%$L`nD9`UP){;_F==e$nNpGt-E(DOhxSkJ z%>DN4*|TT&oZWNIjy%J>@+_k*&~hq7zYm*7zI(X-thSiNezKg+%G5fTG~~x&nM0C> z!mLB*kcoY{Lr-?Fg_^L=iNbn^)Pih=$m^^mE%HLnBTQOMzQx_^@q0rtbxkl>INk9^ zF7mfA79zjNU5eF1bUncV@I2X{o2+5nJ7X+JyLWDnnC9-oxStH=onqgjA~pkAYhABD z3DRc)UIRE!E?K+q3X(UkkaW%KQU3~gT=q9go+Zo6PP5NR5pt`EwXlgSD{sYW^272q zSRJ`tUW8@&2nAX4w%4rYsl50(Su*@++6pT|PPUn8d;}pS%0WSSkaf!EP0M&v5z0a? z<8&7}IH!CAnxB#ABcwqk4EeI6dSRQ|$(AwkfhrtSBvkZBKt@@i!bP9|fO8N0g=xHO02h$>#$|@|0*jN65VdGIv#_CQ=ZJ5U3{`ySJMo!gV!zH(`GY~;h!`CH|CcqLhUC6I5*Oi1%Ic;ee5XWEt6M1I@u zD3~rN;vVTNltlkK)fB4n9^`s3+J@~C2#<0nav+Qnwp);}iOSj-K|w{ukhd{0S;*Jh z?G-b22nwN<^mPPn)D62VNzh1=x)j*#s$ zAO`_Bs&;_m13_=E*E65*_lEfa6;CTu-!^Y=I7Dik?-mVY@ZJ7EC^6m};Ke)_<^!8~ zVVG*MVR#!L-`)H7@7uCw%9NO7L$Hkax_JdCv0!}!)Lwu~0Aj5Xv#FSAdEh;ijK!M9 zf)8eDS~7G;L0C+}+ISLXyZgKSO8TTphhZ)GxHG9#59Bc_8DqQqgit_;C*&=_k%rtp zkDvnyNYEiI1*?_5vbv0FGv#XNA1arSUsUmme@d~ebet_CYr8h+Qqm75y=pvUJe)b2 zlDDspyxz6DaZHzfFm+VNA88nBJ=QpEKGt$YS9&uw_sGWK);A32I)>}tSaQJ_ZCf+C zYHhS(U38r{YS}!R+7s3H+H_k{6#v5t;k<`(&{Go>7*3*qw(kgDGXOBxz z`r;`CGNin$I-ojOc2&<$a440Z(xWuX%xcl9j;M1(bmMnEb-JS+o1&ZiQOmZ`)ZVDR zmlUmCng7oA(Iw5%^psDOb)JHzI#fG(&tVg zY%s=>)5j&W{dzq4L5aQ>>Y<)HS6|eUheq;pn)T8Pl2l4xFq&Gl?1d#P#fy>x%DGsS z+>#{yz2vb<+WCWiQHxrBnUzxdvI+yORsfx(ZpoEj&XE9Z#*|iH&J{a(b6P4Q1?x6T zv*7a3Lb#bW0_02aRAaKu^z9TPCpVVjrpT?07cf3SPPpsw>d1s!p~2nc-nMo4C~@{a z&i)xD-Hx2-U5YhgOh9xB|3>6a;3eHrN)O24q2>>46^@dZgJ&Oudmbxgboy z_@p@0?GDjfy&AOC0DKBV*8r}Q9HADkk9Y)w5)YH@Pd<%5BYI0R;RZL#uY<%qOhSWY z_&6CFd`le#j=29zB)F>pUnM`?Wn`x{y}2ukq0%>YRpB2+Ql64AYMOeH&;=_VCwY7J zG>L&K>i7gkz(7GXRdkMMzG$i#tzy~|0~KOPSOMS%@BsXdOzausTo`9f&|HYKCZA%+ nt-Y38i_QHG0FGNE?s13`ebtEy$^e2z&}?Vo*; zfBt>v+9ZK;c{zWtcox?r;-e5#}uG2r-8x$|2G)U9!B8LX3`+TI1 zs}2N;W!g&vtO#T5c0$t6%U@b`WEHHrX)e5OE}S$knoMv`Xq~WqU3&PftbB!8oFprH znm)VKK=^UaXPl3Qr*hYDeim%`4!$egkpCmj_rm0YGtxUa!;u6(DA=C-3R2mExIpap z1Q%htU<+RdjrL+CR~NxfyHEcnDZ&W5ZBXU1fqr4J`YOKu5Y{YwpccPD3KfNR^(2xX zhr0?_EBQq@;oPJCh%{kakx`X#5vfCpsCJHsL2(mbBI-bAKf2^LxRyGNpP=Lv{(^r+ zfIID$Ff84#Fn)k<4z0M0f8rM8lvSxeM|vK5%FarE!;p09VDW}cFj!vCb#S4)o$JC0 z%a(BXQ$;~>tIi{pD{weIl{}zX7^SQq=UG|f0>-?#vm)lv9JylwS4CEnHXwNmo`|*O z6fAr^e8o=2dN49p=1q^v7;_sv+`!!N|7!-y6JWgJsl}%Me;YPk+o3W=KC`h+3V6H2 z3iqmYaA1W+;YbXhT(O<=_hDw`2>%+6f2W$8;rf=0{c;2k;Kl?+MovG`L2NxikRU`r zaS%NOlxpHALPXs!Mn(f-0=Yv!>ugfqCU~9TAc1Ud09k|&Mbsf*_aG*AFx2Vy2ZjDV z%7c0YyB!W!rs}>#N^1oV!nw-paBz6#N_5 zy?!s>4PUOW<9~+LO&fU{9Bw)$eJo*K8!)doCmKA4GET+HuMH?NS*rn_El-9s8w?y- zsTFQ)Sc0r11B;JGs|qM_z}vg!7pQK2MuVXbXuO$lt9gl9>(-!lyx9tlmKvS|LoF`j zlk{&jwB*5*%FGvFH8aCMT2g2MT*|Vs0B&x`J?geAwX}MvwKRu$V>bym6uTcN&BPPR zk2_&b8rw8CL3OK{C&Ed0I&FD}yi2QzZH^mN6thvq)0hW=IB!wRNJ}w1SZ-0dlRa@T zwlc%&iQAxPv@;gRvROcfBOS4kZZnxyr+9SkOlWU)=!?;k)}zQ}J|ZgLtk=Io$LW}Uw%p!b%<2DeqV z^>^?mM?rD9Dyj=e9yL6>!=jjrRJ=DiHqWDPNtN5lwPvk-{(j1ob8FTq8k?iL$EYo% z;dwc%tu9(FyoE7a66?YJSXmBpCq)DAS&6!yRJnr9bKWKmo-;NJ|MBdW(kJT;@GMrx z8?P|tW0lG*=HsJ^91K?=&WF`0wgaqBdTxbWkJ~xV5~D+#@%L~!!YV$$bGF7hCmuB` z)-@Hv$nloQbw-Cw?v(I_rzFRM2EL+}n02iC*S=r+el<9muxLWNNKgukdr;dJjGH35 z7Xj|Hy;~xtk?qd5^^A0n4)qR)g0{}SP_M9c`$vMl;r=4q@JPre&f_58S2Hs+j?B3# ziq~egVxA;+oPgT7+~~vsVpj>|YGWhzt%9WID5DAxJxUKTx!R_YLM~VHtC7^bs~f)p zL?&O0>LeT!(ROtPd;PxQUQthzEi`Yg5X&`K73}o&2s3G@P6v$$cDoIJR+(a4A@Wde zz+dp30>&$C+Q%xccHWnYK)tmCf?M~&s%>w&Zker6d7|*ulHWT2VqSd9l78D{c}aIv zcdYyalkI_qrPv_Sbfu-t+3vL}mNL)~<=}o*P?xC))ca^7|)K1}01cQ17iZ zy|ry3t^TH=;em^@q@4Q=aKdZhPPpK$YQ1lLQux0P*5Ee7Pb-_U*`%evm@{0*4tcJZ}gSC7`fw?Ru_8$ShS0!`9CY0ACuOkfQ+OcAGC6`Vu8 zcrk%KMs{m@{ksb*+1n+|D`%}heIJZ7vgwwT3SavW!JB_DLFHh!a d(@Ry{G99nxDbuDnp7 +let pid = pid_t(CommandLine.arguments[1])! +let code = CGKeyCode(CommandLine.arguments[2])! +let source = CGEventSource(stateID: .privateState) +for down in [true, false] { + let event = CGEvent(keyboardEventSource: source, virtualKey: code, keyDown: down)! + event.flags = .maskCommand + event.postToPid(pid) + usleep(30_000) +}