diff --git a/scripts/measure/live-code-shell.sh b/scripts/measure/live-code-shell.sh index 0af71e3f33c3..f47de56cbc27 100755 --- a/scripts/measure/live-code-shell.sh +++ b/scripts/measure/live-code-shell.sh @@ -2,7 +2,8 @@ # Live code, measured end to end in the view shell (Prototypes/ViewShell/README.md, "Live code"): # builds the Debug shell, runs it headless with a live folder, edits Badge.swift (a padding and a # colour, which also changes the badge's view type), compiles and drops the patch, and times the -# edit to the redrawn badge's pixels. Then a stored property edit, which must not apply live. +# edit to the redrawn badge's pixels. Then a new file the badge uses (named nowhere: inject.sh takes +# it along), and a stored property edit, which must not apply live. # scripts/measure/live-code-shell.sh OUT (OUT: a folder for the log, the PNGs and result.json) set -euo pipefail out="${1:?usage: live-code-shell.sh OUT}" @@ -22,6 +23,7 @@ shell=$! restore() { kill "$shell" 2>/dev/null || true [[ -f "$out/Badge.swift.orig" ]] && cp "$out/Badge.swift.orig" "$package/Sources/ViewShell/Badge.swift" + rm -f "$package/Sources/ViewShell/LiveCodeProbeDot.swift" rm -rf "$live" } trap restore EXIT @@ -59,25 +61,54 @@ drawn2=$(grep "redrawn generation=2" "$out/shell.log" | sed 's/.*at=//') repatched="no: generation 2 drew the same picture as generation 1" cmp -s "$out/generation-1.png" "$out/generation-2.png" || repatched="yes" -# 3. A new stored property (a SwiftUI `@State`, whose layout the runtime fills in): refused. -sed -i '' -e 's/ let text: String/ let text: String\n @State private var extra = 0/' "$package/Sources/ViewShell/Badge.swift" +# 3. A new file with a new view, used by the badge. Only Badge.swift is named; the build never +# compiled the new file, so inject.sh links it into the patch too. +edit3=$(now) +cat >"$package/Sources/ViewShell/LiveCodeProbeDot.swift" <<'SWIFT' +import SwiftUI + +// Written by scripts/measure/live-code-shell.sh and removed when it ends: a new file in a patch. +struct LiveCodeProbeDot: View { + var body: some View { Circle().fill(.green).frame(width: 6, height: 6) } +} +SWIFT +sed -i '' -e 's/^ Text(text)$/ HStack(spacing: 4) { LiveCodeProbeDot(); Text(text) }/' "$package/Sources/ViewShell/Badge.swift" "$package/inject.sh" --live "$live" "$package/Sources/ViewShell/Badge.swift" | tee "$out/inject-3.log" || true +newfile="not applied" +drawn3="" +if wait_for "redrawn generation=3" 30; then + drawn3=$(grep "redrawn generation=3" "$out/shell.log" | sed 's/.*at=//') + newfile="no: generation 3 drew the same picture as generation 2" + cmp -s "$out/generation-2.png" "$out/generation-3.png" || newfile="applied" +else + grep -q "refused" "$out/shell.log" && newfile="refused" + kill -0 "$shell" 2>/dev/null || newfile="shell exited" +fi + +# 4. A new stored property (a SwiftUI `@State`, whose layout the runtime fills in): refused. +# The generation it would draw is the next one (a refused patch draws none). +next=$(( $(grep -o "redrawn generation=[0-9]*" "$out/shell.log" | tail -1 | sed 's/.*=//') + 1 )) +refusals=$(grep -c "refused" "$out/shell.log" || true) +sed -i '' -e 's/ let text: String/ let text: String\n @State private var extra = 0/' "$package/Sources/ViewShell/Badge.swift" +"$package/inject.sh" --live "$live" "$package/Sources/ViewShell/Badge.swift" | tee "$out/inject-4.log" || true sleep 3 stored="not applied" -grep -q "redrawn generation=3" "$out/shell.log" && stored="applied (unsafe: layout changed)" -grep -q "refused" "$out/shell.log" && stored="refused" +grep -q "redrawn generation=$next" "$out/shell.log" && stored="applied (unsafe: layout changed)" +(( $(grep -c "refused" "$out/shell.log" || true) > refusals )) && stored="refused" kill -0 "$shell" 2>/dev/null || stored="shell exited" cp "$out/Badge.swift.orig" "$package/Sources/ViewShell/Badge.swift" -python3 - "$out" "$build_start" "$build_end" "$edit" "$compiled" "$drawn" "$edit2" "$drawn2" "$repatched" "$stored" <<'PY' +python3 - "$out" "$build_start" "$build_end" "$edit" "$compiled" "$drawn" "$edit2" "$drawn2" "$repatched" "$edit3" "$drawn3" "$newfile" "$stored" <<'PY' import json, sys -out, b0, b1, edit, compiled, drawn, edit2, drawn2, repatched, stored = sys.argv[1:] +out, b0, b1, edit, compiled, drawn, edit2, drawn2, repatched, edit3, drawn3, newfile, stored = sys.argv[1:] r = { "debug_build_s": round(float(b1) - float(b0), 2), "edit_to_compiled_s": round(float(compiled) - float(edit), 2), "edit_to_pixels_s": round(float(drawn) - float(edit), 2), "warm_edit_to_pixels_s": round(float(drawn2) - float(edit2), 2), "second_patch_redrew": repatched, + "new_file_edit": newfile, + "new_file_edit_to_pixels_s": round(float(drawn3) - float(edit3), 2) if drawn3 else None, "stored_property_edit": stored, } json.dump(r, open(f"{out}/result.json", "w"), indent=2)