makepad/examples/gamemaker/resources/template/tools/ag
Admin 4cfb51877c PerfGraph: generic frame profiler widget + Cx perf monitor channels
platform: Cx.perf_monitor — per-frame ring (240) of paint-to-paint gap +
per-channel CPU us. Built-in channels: event dispatch (outermost, minus
app-attributed time), script exec, GC, pass encode, nextDrawable wait.
Apps register custom channels: cx.perf_monitor.channel("physics", rgb).
Off until enabled; hooks in event dispatch, macos repaint, metal draw_pass.

widgets: PerfGraph — corner-pinned live panel (DrawVector strips): frame-gap
bars colored against 120/60Hz budgets with guide lines, stacked per-channel
CPU, legend with averages. Self-positions bottom-right (DrawVector geometry
+ deferred turtle alignment don't mix — no aligning parent).

gamemaker: PerfGraph hovers the game pane (F3 toggles), engine feeds script
+ physics channels (incl. hot-reload evals); engine text overlay moved to
F4; per-phase engine window kept for ag perf / AIGAME_PERF=1; new 'ag perf'
harness verb + template guidance (template CLAUDE.md force-added: runtime
resource, blanket CLAUDE.md gitignore had kept it untracked).

Measured on my-game-5: engine frame CPU ~0.3ms; the hiccup is frame pacing —
the 8ms NSTimer paint clock beats against the 120Hz display, the drawable
pool drifts full and nextDrawable blocks the main thread in a ~25-frame
sawtooth (avg 2-3.4ms, spikes 20-30ms). The graph shows it as red ramps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:10:27 +02:00

95 lines
3.3 KiB
Bash

#!/usr/bin/env bash
# Agent harness for the in-process Game Maker engine. The game runs INSIDE the
# Game Maker app; these verbs talk to it through the .agent/ directory.
#
# tools/ag peek 4 screenshots of the LIVE game + entity state -> .agent/sheet.png
# tools/ag test [frames] [tape] restart + replay an input tape -> .agent/sheet.png + probe.txt
# tools/ag errors the last eval/runtime error (empty output = your edit is live)
# tools/ag logs tail of the game log (eval reports + game.log lines)
# tools/ag perf per-phase frame profile of the live game (~2s window)
set -uo pipefail
PROJ="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
AGENT_DIR="$PROJ/.agent"
mkdir -p "$AGENT_DIR"
die() { echo "ag: $*" >&2; exit 1; }
wait_for_file() { # <path> <seconds>
local deadline=$(( SECONDS + $2 ))
while [[ ! -f "$1" ]]; do
(( SECONDS > deadline )) && return 1
sleep 0.2
done
return 0
}
cmd_peek() {
rm -rf "$AGENT_DIR/live"
: >"$AGENT_DIR/peek_request"
wait_for_file "$AGENT_DIR/live/done" 15 || die "peek timed out — is the Game Maker app running this game?"
# The last PNG lands slightly after done; give the GPU readback a moment.
wait_for_file "$AGENT_DIR/live/f0003.png" 5 || true
sleep 0.3
python3 "$PROJ/tools/sheet.py" "$AGENT_DIR/live" "$AGENT_DIR/sheet.png" 4 4 480 || die "sheet failed"
echo "--- state ---"
cat "$AGENT_DIR/live/state.txt" 2>/dev/null
cmd_errors
}
cmd_test() {
local frames="${1:-120}" tape="${2:-}"
rm -f "$AGENT_DIR/test_done" "$AGENT_DIR/probe.txt"
rm -rf "$AGENT_DIR/cap"
printf '{"frames":%d,"tape":"%s","every":%d}' \
"$frames" "$tape" "$(( frames / 16 + 1 ))" >"$AGENT_DIR/test_request"
# 60 ticks/second plus capture latency.
local budget=$(( frames / 30 + 20 ))
wait_for_file "$AGENT_DIR/test_done" "$budget" || die "test timed out — is the Game Maker app running this game?"
sleep 0.5
python3 "$PROJ/tools/sheet.py" "$AGENT_DIR/cap" "$AGENT_DIR/sheet.png" 8 4 320 || die "sheet failed"
echo "--- probe ---"
cat "$AGENT_DIR/probe.txt" 2>/dev/null | head -40
cmd_errors
}
cmd_errors() {
if [[ -s "$AGENT_DIR/last_error.txt" ]]; then
echo "--- errors ---"
cat "$AGENT_DIR/last_error.txt"
else
# Warnings (unknown option keys etc.) don't fail the eval so they never
# reach last_error.txt — count them since the last eval, so "no errors"
# can't hide a misspelled option.
local warnings
warnings=$(awk '/ eval #/{n=0; next} /\(ignored\)/{n++} END{print n+0}' \
"$AGENT_DIR/game.log" 2>/dev/null || echo 0)
if [[ "${warnings:-0}" -gt 0 ]]; then
echo "--- no errors ($warnings warnings, see ag logs) ---"
else
echo "--- no errors ---"
fi
fi
}
cmd_logs() {
[[ -f "$AGENT_DIR/game.log" ]] || { echo "no log yet"; return 0; }
tail -40 "$AGENT_DIR/game.log"
}
cmd_perf() {
rm -f "$AGENT_DIR/perf.txt"
: >"$AGENT_DIR/perf_request"
# The engine answers with the next COMPLETED ~2s profiler window.
wait_for_file "$AGENT_DIR/perf.txt" 8 || die "perf timed out — is the Game Maker app running this game?"
cat "$AGENT_DIR/perf.txt"
}
case "${1:-}" in
peek) cmd_peek ;;
test) shift; cmd_test "$@" ;;
errors) cmd_errors ;;
logs) cmd_logs ;;
perf) cmd_perf ;;
*) sed -n '2,10p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' ;;
esac