Clocked piano/ironfish/drum rack, program-bus mix, splat/mixer/music
updates. Silence the unused warnings that show up in `cargo check -p
makepad-vj --release`.
* View: an on_item_tap hook for script-rendered lists
Rows built by `on_render` can't carry `on_click` closures (they stop the
list re-rendering), so lists had no way to be tappable.
* `on_item_tap: |index|` on the container fires with the direct child
under a tap
* Runs after the scroll bars with capture overload, so a press still
starts a drag scroll and a Button child keeps its own click
* View: on_item_tap hit-tests rows with clipped_rect, so scrolled lists map to the right row
* View: a press that catches a fling never counts as an item tap
`call_script_fn` looked names up in the module body scope, but a
`let`/`fn` that shadows a name already in scope opens a child scope,
and everything the script defines after it lands there, invisible from
the module scope. The Splash prefix's own `let fs` / `let host` can be
that shadow, so app hooks never resolved.
* The VM records the scope a root frame ended in (`ScriptBody::end_scope`)
* Splash looks hooks up there, falling back to the module scope
- makepad_test/runtime.rs: forward NIGIG_TEST_MODE from host env to the
Android app via 'am start' intent extra; add wait_timeout (60s) used by
wait_visible/wait_hidden/wait_count; make query_widgets tolerant of
snapshot timeouts; grant READ_CONTACTS during adb setup
- makepad-platform android_jni.rs: read makepad.NIGIG_TEST_MODE intent
extra and surface it as the NIGIG_TEST_MODE env var via apply_studio_env
- cargo_makepad compile.rs: support verbatim custom AndroidManifest.xml in
addition to the templated variant
- makepad-xr xr_root.rs: add ortho camera controls (ortho, ortho_height,
min/max), derive Debug on XrCamera
- docs: ANDROID.md and DESKTOP_VISIBLE.md for makepad_test
The Android platform never sent BeforeStartup or AfterStartup messages
via the studio websocket. Desktop platforms send these through their
stdin event loops, but Android uses websockets instead of stdin.
Without AfterStartup, the hub never broadcasts AppStarted to UI
clients, causing makepad-test to time out waiting for app startup.
Adds the Android test runtime to makepad_test: builds the APK with
cargo-makepad's standard Java path, installs and launches via adb with
makepad.STUDIO_* intent extras (incl. STUDIO_BUILD), connects the app to an
in-process hub over adb reverse, and waits for startup + responsiveness.
Adds clean in-process hub shutdown (HttpServerHandle + GatewayHandle Drop)
and the STUDIO_BUILD intent parsing on the app side. No native-activity or
NDK APK compilation code is included.
MAKEPAD_TRACE=frames prints, every two seconds per window, what woke
each tick (the waitable beat, a timeout, a message, a timer, a drain,
the display link), the NextFrame gap histogram, the present gap
histogram, the flip lead and refused presents — one bool check per
tick when off, readable over --remote. It was written to chase the DJ
beat wave's judder on Windows and found the measurable defect on the
way: when no window could present (minimised, hidden, or a disconnected
RDP session where the compositor refuses every present), the paint
tick slept a millisecond and re-ran, dispatching NextFrame and
redrawing at about 600 Hz for frames nothing would show. That case is
paced at 8 ms now, the same cadence as video pacing, the idle beat and
a hidden macOS window; a visible window that drops a frame keeps its
1 ms retry.
The judder itself could not be measured hidden — a disconnected RDP
session presents nothing at all — and the reading is that a connected
RDP session's bursty ~30 fps frame delivery is what the person saw;
the trace settles it in one visible run. platform 153 tests; both
desktop targets check.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* remote: honor MAKEPAD_REMOTE in requested(), and hush the close notices when the bridge is off
`requested()` only scanned argv, while `requested_bind()` also reads
MAKEPAD_REMOTE. So `MAKEPAD_REMOTE=1` started the bridge but everything
keyed off `requested()` still said no. Just delegate, so there's one
answer to "did this process ask for the remote bridge".
Also stop printing `[makepad-remote] user closed window ...` to stdout
from every app on every window close -- that line is for the agent
driving the app, so only print it when the bridge is actually up. The
log ring still gets it either way, for /log.
* devtools: put the F10/F12/Shift+F12 overlays behind an opt-in
Three dev tools are currently wired into every app with no way to turn
them off, each on a bare function key:
F10 the exploded draw-list view. Intercepted in
Cx::call_event_handler *before* the app's handler, and once
it's up it also eats Escape, the arrows, +/-/0, I and H --
no modifier needed -- plus every drag outside the flat band.
F12 the design tweaker, a child of every Window. Once it's up it
swallows every pointer event over the body, so the app looks
frozen to the mouse.
Shift+F12 the screen recorder, which starts writing mp4s to disk.
None of that is something a shipped app wants a user to find by accident,
and there was no flag, env var or property to stop it.
So: one gate, platform/src/devtools.rs. `--devtools`, or
MAKEPAD_DEVTOOLS=1, and --remote implies it since the /snap + /click loop
drives the tweaker. An explicit MAKEPAD_DEVTOOLS=0 wins over all of it,
which also keeps the off path testable under --remote.
Only the hotkeys are gated, not the tools. Cx::sploded_toggle,
set_tweak_on and ScreenCap::toggle are untouched and still public, so an
app that wants any of this puts it on a key of its own choosing -- that's
the app deciding, rather than a key nobody knew was bound.
Gating F10 and F12 is enough to reach all of it: everything else these
two claim sits behind `sploded.active` / `tweak_is_on()`, and with the
hotkeys gated the only remaining ways in are the /tweak routes (already
--remote, which implies devtools) and an app's own call.
* text_input: drop the Ctrl+Enter submit clause again
`|| mods.control` made Ctrl+Enter submit a multiline input. On
Linux/Windows that's already what is_primary() means, so it changed
nothing; on macOS it turned Ctrl+Enter from "insert a newline" into
"send", which is a surprise in the middle of a chat app's composer.
The comment right above it already described the old behavior, so this
puts the code back in line with it.
Cx::memory_budget_bytes() is set once at startup from the device
(ProcessInfo on macOS/iOS, ActivityManager on Android, GlobalMemoryStatusEx
on Windows, /proc/meminfo on Linux, the wasm maximum on the web) through
one policy: desktop keeps 1536 MiB unless RAM is under 8 GiB (then RAM/4),
mobile takes RAM/4 clamped to 384..1536 MiB. The map's four byte budgets
are fractions of it (upload 1/64, pending 1/4, tile cache 25/32, HTTP
cache 5/32; the tile cache caps at 1/2 on web heaps under 1 GiB) instead
of constants. At 1536 MiB they are the binary sizes 24/384/1200/240 MiB,
about 5 % above the old decimal constants. Harness totals unchanged; Metal
grab and web GPU gate within noise (BUDGET1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
platform: an in-app drag works without an OS drag session, so effect tiles drop into the channels on the web
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
The first WebGL frame compiled every registered program synchronously;
on ANGLE-Metal that is 8.6 s of from_wasm dispatch for the DJ app (35 ms
of wasm), during which no timer, fetch completion or animation frame can
run. SwiftShader hid it by compiling at first draw. Programs are now
queued only when a draw list references them and linked with
KHR_parallel_shader_compile; the first draw waits only for the programs
it needs. DJ app on the GPU: localhost fetches 8.7 s -> 0.2 s, 39
programs ready in 300 ms; route first draw at 224 ms (STARTUP1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A runtime the platform did not create (the asset client builds its own)
has no wake fn, so on the web its responses sat until an unrelated timer
pumped the event loop. EventSink::emit now sets the UI signal as well;
the DJ app polls its store session on Event::Signal instead of waiting
for the next poll tick.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Roads move from eight f32 lanes to a 28-byte RoadVertexTyped (i16 anchor
at 1/64 tile unit, f16 offset / depth / uv pairs, unorm8 colour, f16
params, exact f32 deck), ground fills and roofs to 16 bytes (i16 anchor,
unorm8 colour, f16 params; u16 depth ticks for fills, exact f32 height for
roofs); indices are u16 whenever a stream has fewer than 65,536 vertices.
The three shaders read the typed POD fields directly (the fetch converts),
uploads go through Geometry::update_typed with the shader's layout, the
space-warp subdivision decodes / interpolates / re-encodes the typed
records, and a tile that lands before its shader has drawn waits in the
pending queue instead of panicking. The shader compiler types a packed
field access as vec2f / vec4f, and the Metal name table gains the packed
short / ushort / uchar / char vector names.
Harness: 249.6 -> 215.5 MiB for the 25 start-view tiles, 384 ms/tile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
std's wasm allocator is dlmalloc behind one global spin lock taken by every
thread on every allocation, so eight workers baking map tiles serialised on
it: a tile that bakes in 0.4 s natively took 15-20 s (mvt-parse 0.7-4.5 s,
detail-merge 2.3-12.5 s), and the whole start view needed 150 s. This
installs a #[global_allocator] for wasm32+atomics: per-thread size-class
free lists (16 B .. 32 KiB) carved from 64 KiB chunks of the dlmalloc heap,
remote frees pushed onto per-chunk Treiber stacks and drained by the owning
thread, exit-safe reclamation, the main thread's caches pre-filled at
startup so its hot path never touches the global lock.
Measured on the same capture with 8 workers: mvt-parse 7-68 ms, detail-merge
27-415 ms per tile (native 4-17 / 23-70), zero traps, zero Atomics.wait,
the full start view. The two-worker stop-gap in worker_count goes with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Draw shaders can declare geometry POD fields as F16x2/F16x4, U16x2/I16x2
(plain or normalised) and UNorm8x4/SNorm8x4; the fetch converts them to
vec2f/vec4f on Metal and WebGL2 (per-attribute vertexAttribPointer with
type, normalised flag, byte stride and offset), the headless JIT decodes
the same formats, and Vulkan/OpenGL/D3D11 refuse compact layouts with a
logged skip rather than drawing garbage. Attribute packing computes
physical offsets with natural alignment for the whole record; the all-F32
path stays byte-identical (stride = slots * 4, packed_geometry_N on GL).
Geometry::update_typed / update_typed_with_recycled_buffers take
IndexData::{U16,U32} plus a byte vector and the shader's input layout;
updates are validated (stride, whole vertices, index range) and every
geometry carries its index width and a layout signature that survive
releasing the CPU staging, so a draw checks layout and index type against
the resident buffers, never the staging enum. Compact fields are rejected
in instance PODs and in nested aggregates at shader validation; SNORM
minima clamp to -1 like WebGL2.
On Metal the decoded (logical) vertex is reached through the shader
context like the instance is, so every shader function that reads
geometry compiles.
No map stream is converted yet; that is the next cut.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
std's wasm allocator spins, so the Atomics.wait that killed one WebGL2
run in four came from std Mutex / Once contended between the browser main
thread and a tile worker. Every lock the UI thread shares with a worker
(task pool state, cancellation generation, task results, the scheduler,
the web request table, the archive read states and shard file cache) now
goes through lock_from_ui, which spins on try_lock on wasm32 and is a
plain lock elsewhere. The icon mesh cache, icon slot table and tag-key
whitelist are warmed on the UI thread before the tile pool starts so no
Once can wait either.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>