Where the web target stops, including the one that is a missing include
BUILT.md gains the section on the third target and corrects the claim it already carried: emscripten_set_main_loop had the browser fact right and drew the wrong conclusion, because asyncify answers the same fact without cutting main in half. NEXT.md gets the four holes. sand.flan has no web build, and the proximate cause is that vendor/agent/flan_agent.c:426 uses struct timeval without pulling in sys/time.h, which glibc gives it transitively and emscripten does not; sand's main calls agent/start unconditionally so Reach cannot prune it. Beneath the include is the decision worth making rather than patching around: the agent is a socket server and the browser has no sockets, so the honest fix is to refuse vendor:agent on a web target the way --dev is refused. Assets are two questions and only the easy one is about emscripten. --embed-file is a linker argument and so already expressible as an @web line. The hard one is that the file doing (rl/load-texture "brush.png") is structurally the one file that cannot say so: Load hands out lflags only for a directory package, and main is not exported, so a program can never be one. No flag was invented for it. And nothing has been opened in a browser, asyncify's cost is quoted rather than measured, and audio and threads on web are untried.
This commit is contained in:
parent
8d048123ca
commit
3e15328acd
73
BUILT.md
73
BUILT.md
@ -242,7 +242,9 @@ Two claims that got run together in an earlier note, for the record:
|
||||
- *raylib does not work on wasm* — false. It works through emscripten. What is true is that it does not work on the
|
||||
**wasi** path, which is what the headless table targets, and which has no GL and no browser.
|
||||
- *a game loop cannot be expressed on wasm* — false. The browser cannot be blocked, so a web build drives the loop with
|
||||
`emscripten_set_main_loop` instead of a `while`. That is a different `main`, not a different program.
|
||||
`emscripten_set_main_loop` instead of a `while`. ~~That is a different `main`, not a different program.~~ **The premise
|
||||
held and the conclusion did not.** It is the same `main` and the same program: `-sASYNCIFY` answers the same browser
|
||||
fact without cutting anything in half. See "The browser is the third target" below.
|
||||
|
||||
**Three edits were made to sand.flan's own text** when it was ported, and they are language decisions rather than fixes:
|
||||
|
||||
@ -1221,5 +1223,70 @@ an `ExperimentalWarning` to stderr on every run and the harness compares combine
|
||||
one (same reason), and `flan run --target=` (a `.wasm` is not something this host execs — build it and point a runtime
|
||||
at it).
|
||||
|
||||
Still open: raylib on wasm, which plan.org wants through emscripten and its own sysroot. wasi-sdk is right for the
|
||||
headless table; it is not necessarily right for the eventual game build.
|
||||
~~Still open: raylib on wasm, which plan.org wants through emscripten and its own sysroot.~~ **Done — and it is a third
|
||||
target, not a mode of this one.** wasi-sdk is right for the headless table and was never going to be right for the game
|
||||
build. See the next section.
|
||||
|
||||
## The browser is the third target
|
||||
|
||||
`flan build --target=web` produces a page, its JS and a `.wasm`, and a raylib example opens in a browser from source
|
||||
that was not touched. The two wasm targets share the word and almost nothing else, so `is_wasi` and `is_web` are
|
||||
separate predicates and `is_wasm` is their union — the union is exactly the set of facts about the *machine* (32-bit
|
||||
pointers, no `dlopen`), which is what the refusals are about, and nothing else is shared.
|
||||
|
||||
**The compiler is `emcc`, not `clang`, and that is the whole of the sysroot story.** Everything the wasi target has to
|
||||
find by hand — a sysroot, a builtins archive, a shadow resource directory, the `__main_argc_argv` shim — is what emcc
|
||||
*is*. `target_flags` for `web` is the empty list; the only thing checked is that emcc exists, refused by name where the
|
||||
reason can say so. The one fact that had to be true for any of this: **emcc takes a `.ll` on its command line**, which
|
||||
it does, so `Emit`'s output needs no change and the IR stays target-independent. The object cache keys on the compiler
|
||||
binary's path, size and mtime as it always did — now of *whichever* compiler the target uses, so an emcc `flan_rt.o`
|
||||
and a clang one cannot collide.
|
||||
|
||||
**The main loop: `-sASYNCIFY`, not `emscripten_set_main_loop`.** The older note above had the browser fact right —
|
||||
it cannot be blocked — and drew the wrong conclusion from it. `emscripten_set_main_loop` wants the loop body as a
|
||||
callback, so every one of the eleven examples that writes
|
||||
|
||||
```lisp
|
||||
(until (rl/window-should-close?) ...)
|
||||
```
|
||||
|
||||
would have to be split by hand into an init and a tick, and the web program would stop being the native program.
|
||||
Asyncify rewrites the module so a call can suspend across a return to the event loop, and raylib's web platform is
|
||||
built for precisely that: `WindowShouldClose()` on `PLATFORM_WEB` is an `emscripten_sleep(16)` that then returns false
|
||||
(raylib 5.5, `platforms/rcore_web.c`, read rather than assumed). So the loop yields once a frame at a call it already
|
||||
makes, and **no example changed a character**. The price is real and is paid by every web build: asyncify instruments
|
||||
the whole module, roughly doubling code size. It is not applied per-program because "does this program block" is not a
|
||||
question `Build` can answer, and a flag set that varies per program is a cache key that varies per program.
|
||||
|
||||
**`link` lines can be addressed to a target.** `vendor/raylib/link` named `libraylib.so.550`, which exists on the host
|
||||
and nowhere else. A line may now carry `@native`, `@wasi` or `@web`, an untagged line applies everywhere — which is
|
||||
what every existing `link` file already is — and `${NAME}` expands from the environment. The selection happens in
|
||||
`Build` and not in `Load`, which is where the file is read, because **`Load` resolves imports before a target is
|
||||
chosen**: the same program is built for both, and a package's linker arguments arrive here as a flat list of strings.
|
||||
`Load`'s part in this is to pass the lines through untouched, which it already did.
|
||||
|
||||
**raylib for the browser is built, not installed.** No emscripten port provides it (`emcc --show-ports`: there is
|
||||
`contrib.glfw3` and no raylib), so `vendor/raylib/build-web.sh` clones raylib at the **5.5** tag — the one whose
|
||||
`.so.550` the host links, because `raylib.flan` carries raylib's struct layouts and enum values and two targets built
|
||||
from different raylibs would disagree about them in silence — and compiles the seven modules with
|
||||
`-DPLATFORM_WEB -DGRAPHICS_API_OPENGL_ES2` into one archive under `vendor/raylib/web/` (gitignored). `rglfw.c` is not
|
||||
among them: the web platform uses emscripten's own GLFW port, which is why `link` carries `@web -sUSE_GLFW=3`. No
|
||||
headers are installed, for the reason the host build needs none — the generated shim declares its own prototypes.
|
||||
|
||||
**The HTML shell is a string in `Build`, not a file in the tree**, for the same reason `Runtime_src` is: it has to be
|
||||
wherever the compiler is, and a build that cannot find its own shell fails for a reason nobody spelled. It is a canvas
|
||||
and a `Module.print` that puts stdout on the page; `FLAN_WEB_SHELL` replaces it. `--shell-file` is passed only when the
|
||||
output is a `.html`, because emcc accepts and ignores it otherwise.
|
||||
|
||||
**Refused by name, inherited whole:** `--dev`, `--debug`, `Build.shared` and `flan run --target=` are refused for
|
||||
`web` exactly as for `wasm32`, each naming `web` rather than `wasm32` in the message. `--sanitize` is refused too, but
|
||||
the web half of that refusal is weaker than the wasi half and says so: emscripten *does* ship an ASan, and nothing here
|
||||
has ever run it. A sanitizer that has never been run is one whose silence means nothing.
|
||||
|
||||
**What the test can honestly check.** `test/test_web.ml` is headless and permanently so. It probes — emscripten may not
|
||||
be installed, and the raylib archive is not in the tree — and skips with the reason rather than going red. What it
|
||||
asserts: the three files exist, the module starts with `\0asm`, the page references its own JS and carries the canvas,
|
||||
and node runs the emitted JS and gets `ok`. For raylib it builds `core-basic-window.flan` unchanged and then reads the
|
||||
module for the two things that would be false if the mechanism were wrong: an `asyncify_start_unwind` export, and a
|
||||
`glViewport` import that can only have come from raylib's web platform. Import and export names are plain strings in
|
||||
the binary, so this needs no wasm reader.
|
||||
|
||||
38
NEXT.md
38
NEXT.md
@ -295,6 +295,44 @@ crosses as a parameter — a C function that returns one returns something Flan
|
||||
the shape the language already has. Three constructs unexercised anywhere else in the repo worked first try: a fixed
|
||||
array with a struct element, a 2-D struct array, and `[N string]` as both `defconst` and mutable `defvar`.
|
||||
|
||||
### The web target: what it does not reach yet
|
||||
|
||||
`flan build --target=web` works, a raylib example builds unchanged and `test/test_web.ml` is green — see BUILT.md,
|
||||
"The browser is the third target", for the mechanism and why asyncify rather than `emscripten_set_main_loop`. Four
|
||||
things it does not cover.
|
||||
|
||||
**1. `sand.flan` has no web build, and the cause is one missing `#include`.** `vendor/agent/flan_agent.c` does not
|
||||
compile under emcc: *variable has incomplete type 'struct timeval'* at line 426, because emscripten's headers do not
|
||||
pull `<sys/time.h>` in transitively the way glibc's do. `sand.flan`'s `main` calls `(agent/start ...)`
|
||||
unconditionally, so `Reach` cannot prune the package, so the flagship program stops at that error — even without
|
||||
`--dev`. Beneath the include is a structural fact worth deciding rather than patching around: **the agent is a socket
|
||||
server and the browser has no sockets**, which is the same family as the `--dev` refusal. So the two fixes are not
|
||||
equivalent — add the include and the agent compiles into a web build that can never accept a connection, or refuse
|
||||
`vendor:agent` by name on a web target the way `--dev` is refused. The second is the honest one. Neither was taken
|
||||
here: `vendor/agent/` belonged to another lane this session.
|
||||
|
||||
**2. Assets are two questions and only one of them is about emscripten.** `sand.flan` does
|
||||
`(rl/load-texture "brush.png")` against a bare relative path.
|
||||
|
||||
- The easy half: a bare relative path has no meaning on a target with no filesystem. emscripten's answer is
|
||||
`--embed-file` or `--preload-file` into MEMFS, and both are *linker arguments*, so they are already expressible as an
|
||||
`@web` line in a package's `link` file. No new mechanism is needed for a package.
|
||||
- The hard half, and the actual design question: **the file that needs the asset is structurally the one file that
|
||||
cannot declare it.** `Load` hands out `lflags` only for a directory package (`one_file` → `[]`), and `main` is not
|
||||
exported, so a program can never be a package. The program doing the `load-texture` therefore has no link channel at
|
||||
all. Answering this means either giving a single-file program a way to carry build arguments, or making assets their
|
||||
own declaration rather than a linker flag. No flag was invented for it here.
|
||||
|
||||
**3. Nothing has been opened in a browser.** The test is headless and permanently so: it asserts the artifact's shape,
|
||||
the `asyncify_start_unwind` export and the `glViewport` import, and that node runs the emitted JS. Whether the canvas
|
||||
actually paints is unverified by anything in CI, and a human should look once.
|
||||
|
||||
**4. Unmeasured and untested.** Asyncify's cost is quoted from emscripten's documentation (roughly a doubling of code
|
||||
size) and not measured here, and no frame time on web has been taken at all. raylib's audio and any use of threads on
|
||||
the web target are untried. And a **wasi** build that reaches raylib now fails on undefined symbols rather than on a
|
||||
missing `-l:libraylib.so.550`, because that line is tagged `@native` — the same error one step later, and a worse
|
||||
message.
|
||||
|
||||
### `break`, and why it was not built
|
||||
|
||||
Settled, so the next attempt is cheap rather than a rediscovery:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user