- README: a build quick-start for macOS and Windows, the honest Linux
story, what CUDA is for and how to install the separation model.
- AGENTS.md: the `--remote` control surface protocol, so the harness it
documents is usable without reading platform/src/remote.rs.
- Cargo.toml: workspace membership for the crates this series adds and
removes.
- tools/: the Windows box scripts (wincmd, winps and friends, winrun) and
remote_smoke.sh — how a build gets driven on a remote machine.
- apps/asset-server: the standalone server binary and its README.
- Small follow-ups in libs/{windows,apple_sys,makepad_test,mbtile_reader,
converse} and apps/route, plus .gitignore and makepad.splash.
7.2 KiB
makepad-asset-server — the standalone Asset Server host
The asset store is inherently multiplayer. asset-ui, the VJ, the sandbox and headless workers are all clients of one catalog, and they open and close independently of each other. So the catalog's lifetime must not be any one client's lifetime.
Historically the Asset UI embedded the server: it took <root>/server.lock,
served the catalog, ran the chat broker and the events hub — and every time
that window was closed or rebuilt, every other connected client lost the
store mid-session (503 state unavailable). This binary is the fix: the same
service, in its own process, that survives every window.
cargo build --release -p makepad-app-asset-server
./target/release/makepad-asset-server
With no flags it serves the checkout's standard store root
(local/asset-ui/asset-server, or $AI_CONTENT_ASSET_ROOT) on ephemeral
ports, announces itself on the LAN beacon, publishes
local/ai_content_library, and coordinates fleet jobs. --help lists every
flag.
What this process carries
| Part | Where it lives | Notes |
|---|---|---|
| Catalog + CAS, control/data planes | AssetServer::start |
search, blobs, ranges/ETags, retire |
| Auth, tokens, grants | ” | --root/admin-token bootstrapped at start |
| Job queue, worker/lease protocol | ” | plus GET /v1/job-profiles |
| Chat broker (+ client-executed tool parking for game sessions) | ” | --chat-fleet / LAN fleet discovery |
| Games publish path, operations, import routes | ” | |
Committed events hub (/v1/events) |
” | every client's live view |
| LAN discovery beacon | ” | --no-beacon to stay silent |
| Lease janitor + bounded blob GC | ” | runs whether or not anyone polls |
| ai-content library publisher | makepad_asset_importer::watch |
--library / --no-library |
| GPU-fleet job coordinator + profile announce | makepad_asset_importer::gen_service |
--no-jobs / --no-announce / --fleet |
The last two are the loops the Asset UI used to run only while it was
hosting. They are headless — no Cx, no window, no GPU surface — so they
belong beside the server, and running them here is what makes a UI-less
deployment a complete fleet citizen. Without the coordinator, jobs any client
enqueues sit at "waiting for agent" forever.
What deliberately stays client-side
These are content-deriving loops that need resources only a UI process has. They reach the catalog as ordinary clients; moving them here would mean giving a headless daemon a window.
- Thumbnail / preview backfill (
apps/asset-ui:thumbnail_renderer.rs, the splat + mesh offscreen renders). Needs a realCx, render passes and GPU readback. - Classic-game import (
import_classic.rs) and pack import wizards — interactive, and their icon bakes are GPU renders. The one-shot headless equivalents already exist asmakepad-asset-importer --import-pack / --import-games / --import-music / --import-ai-librarywhen a batch run is wanted. - Stems / lyrics analysis bake (
analysis.rs) — a large local model on the operator's machine, driven from the surface that asks for it. - Generation pipelines driven from the Create surface (
pipeline.rs) — these are requests; the queue and the dispatch live server-side.
Recommended deployment
One daemon, every app attached:
# 1. the store (leave it running; it survives every UI restart)
./target/release/makepad-asset-server > /tmp/asset-server.log 2>&1 &
# 2. every client, told never to hold the root itself
export ASSET_UI_ASSET_EMBED=never
./target/release/makepad-app-asset-ui
./target/release/makepad-vj
ASSET_UI_ASSET_EMBED=never (aliases: no, off, 0, false, attach,
client) makes the Asset UI a pure client: it never takes
<root>/server.lock, at startup or during a succession. Without it the app
still attaches when the daemon holds the root — but whichever process starts
first wins the lock, so a UI launched before the daemon would put the store
back inside a window.
The VJ and the sandbox are already pure clients; they need no flag.
How a client finds the server
Three ways, in order of precedence:
ASSET_UI_ASSET_SERVER=ip:controlport:dataport— an explicit pin.<root>/listen,<root>/server-id,<root>/admin-token— what the daemon writes at startup. This is the attach path, and it is why the default bind is0.0.0.0:0: the file is the address of record.- The LAN discovery beacon — how a client on another machine finds it with no configuration at all.
What survives what
- Closing / rebuilding / crashing any app — the server keeps serving. Every other client stays connected; nothing is lost.
- Restarting the daemon — attached clients notice the silence within
~3.5 s, say so honestly in the status chip, and rejoin the new process as
soon as it rewrites
listen. Cursors and leases are re-established; the events journal reports an explicitgaprather than a silent hole. - A daemon crash with
EmbedPolicy::Auto— an attached Asset UI succeeds it (takes the lock, starts the loops, reconnects to itself). This is the transition-mode safety net, not the deployment. - A daemon crash with
ASSET_UI_ASSET_EMBED=never— no app fills the vacancy; clients wait and rejoin whoever takes the root next. Restart the daemon. - Two daemons on one root — refused immediately and by name
(
server root: locked by another server process). One process per root is a law: the job routing metadata assumes a single enqueuer, and two writers over one WAL catalog would be two sources of recovery truth.
Bouncing the daemon safely
# find it (it holds the lock and wrote the listen file)
cat local/asset-ui/asset-server/listen # ip:control:data
pkill -TERM -f 'makepad-asset-server' # clean shutdown: joins every thread
./target/release/makepad-asset-server > /tmp/asset-server.log 2>&1 &
SIGTERM/SIGINT shut down in order: background loops first (so nothing is
still publishing into a closing catalog), then the planes, then the state
thread. Startup logs what recovery found — recovered N cas temps / M leases
— which is the honest report of what the previous life left behind.
Isolated instances
Never point a scratch instance at the live root, and never let one beacon:
./target/release/makepad-asset-server \
--root /tmp/scratch-store --work /tmp/scratch-work \
--no-beacon --no-jobs --no-library
--no-beacon keeps peers hunting for the real store from finding the scratch
one; --no-jobs keeps it from claiming the real fleet's queued work.
Tests
cargo test -p makepad-app-asset-server --release covers the composition:
the host serves a catalog over real sockets and shuts down cleanly, a second
host on one root is refused by name, a missing library directory never costs
the catalog its server, and the defaults are the deployment defaults. The
parts themselves are covered where they live — libs/asset/store (HTTP,
chat, events, jobs, security, operations suites) and libs/asset/importer
(watch, coordinator, gen-service). The attach/succession contract is in
apps/asset-ui/src/asset_store_state.rs.