Say where sand.flan lands for the browser, and how to open it
BUILT.md gains the section: why the path had to become an embed, the rule for a target-tagged .c file, why the agent is a no-op there and why that is not the barf decision reversed, the exact commands including the .html output name and the server a wasm module needs, and the four things only a human opening it can settle. NEXT.md strikes web blocker 1 and rewrites blocker 3 — nothing has been opened in a browser is still true, and is now the only thing left.
This commit is contained in:
parent
ce346dd972
commit
c0bf4ac34c
115
BUILT.md
115
BUILT.md
@ -1795,3 +1795,118 @@ and node runs the emitted JS and gets `ok`. For raylib it builds `core-basic-win
|
||||
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.
|
||||
|
||||
## sand.flan in a browser
|
||||
|
||||
**The flagship program builds for the browser and the artifact opens.** Three things were between it and the target,
|
||||
and none of them was the `#include` the earlier note named.
|
||||
|
||||
**The brush was a path, and a path is what cannot work.** `(rl/load-texture "brush.png")` hands raylib a filename to
|
||||
open; a bare relative path has no meaning where there is no filesystem, so raylib would have opened nothing and the
|
||||
cursor would simply have been missing. It is `(embed "brush.png")` now, decoded by a new binding —
|
||||
`LoadImageFromMemory`, which completes the chain embedded bytes -> `Image` -> `Texture2D` that `LoadTextureFromImage`
|
||||
already had the other half of. The declaration is `(Ptr u8)` plus an explicit `i32` count, because `shim.ml` refuses a
|
||||
slice parameter and its refusal says exactly that; the Flan wrapper beside it takes the slice apart, which is the
|
||||
idiom `collision-point-poly?` and `load-font-ex` already established. One decode serves both textures now: the
|
||||
unflipped upload first, then `ImageFlipHorizontal` in place, then the mirrored upload — **the order is load-bearing**,
|
||||
and if the two badges look the same it was swapped.
|
||||
|
||||
`load-texture` and `load-image` lose their only call site in this repository by this change. That is deliberate rather
|
||||
than an accident of editing: a path-based load is the one shape the browser cannot have, and the bindings stay for the
|
||||
desktop programs that will want them.
|
||||
|
||||
The embedded path resolves **relative to the file the form is written in**, which is why
|
||||
`test/programs/sand-headless.flan` still works: it reaches `sand.flan` through `../../` out of a sandboxed `_build`,
|
||||
and the PNG is found beside `sand.flan` and not beside the working directory. `test/dune` therefore lists `brush.png`
|
||||
as a dependency of every stanza that builds sand — an embed is read by the *checker*, so it is a build input and not a
|
||||
run-time one. The same fact reached `test_session`'s `C-c C-k` case, which re-evaluates sand.flan's whole text: it now
|
||||
passes `~origin`, which is the buffer path both editor paths already send, because the default `<eval>` origin would
|
||||
resolve the embed against the working directory instead.
|
||||
|
||||
**A package's C may be addressed to one target, the way a `link` line already could.** `Load` collects a package's
|
||||
`.c` files by listing the directory, and there is nowhere in a directory listing to put a tag except the name, so the
|
||||
tag goes there, before the extension:
|
||||
|
||||
```
|
||||
vendor/agent/flan_agent.c compiled everywhere, unless displaced
|
||||
vendor/agent/flan_agent.web.c compiled for the browser, and displaces the above
|
||||
```
|
||||
|
||||
One rule: **a tagged file is compiled only on its own target, and there it replaces the untagged file of the same base
|
||||
name.** Untagged is the default and every existing package is untagged, so nothing that did not opt in changed.
|
||||
Replacement rather than the pure tagging Go's `_windows.go` and Odin's `file_js.odin` use, and the difference is the
|
||||
point — pure tagging would mean renaming `flan_agent.c` to `flan_agent.native.c` to teach the package about a target
|
||||
it had never heard of, and this way a package gains a target by gaining a file. The selection is in `Build` and not in
|
||||
`Load`, for the reason `select_lflags` gives: `Load` resolves imports before a target is chosen.
|
||||
|
||||
**The dev agent on the web is a no-op, and it is not the `barf` decision being contradicted.** The compile error that
|
||||
led here — `struct timeval` incomplete, because emscripten's headers do not pull `<sys/time.h>` in transitively — is
|
||||
the surface. Underneath: *the agent is a socket server and a browser has no sockets*. Adding the include produces an
|
||||
agent that compiles, links, starts and can never accept a connection.
|
||||
|
||||
Refusing `vendor:agent` on a web target was the other candidate and is ruled out by arithmetic, not taste. Flan has no
|
||||
conditional compilation, `sand.flan` calls `(agent/start ...)` unconditionally, and `Reach` cannot prune a package
|
||||
something reachable calls into — so a build-time refusal means the flagship program does not build for the browser at
|
||||
all without being edited into a second program. **A refusal is only honest when the caller has a way to not ask.**
|
||||
|
||||
The two decisions look contradictory and are not, and the difference is *what the caller loses*. `barf` is asked to
|
||||
make something durable; a no-op returns success to a program that now believes the bytes are on disk, and the loss is
|
||||
real, is the user's, and is discovered later or never. The agent is asked to accept redefinitions from an editor; on
|
||||
the web there is no editor, no socket and no session — `--dev` is refused by name on every wasm target, so a web build
|
||||
has no cells to install a redefinition into even if one arrived. **Nothing is lost because there was never anything
|
||||
there.** `sand.flan` already says the same about a *native* release build, at the call site: "Building without `--dev`
|
||||
is fine — nothing has cells to install into, so a module is refused on the listener thread and the loop never
|
||||
notices." A web build reaches that outcome by a shorter route. `start` returns `-1`, which is what `flan_agent.c`
|
||||
returns for a path it cannot bind; `poll` and `wait` return 0, which is what the native build returns on every frame
|
||||
nothing arrived on. The whole argument is written at the top of `vendor/agent/flan_agent.web.c`, where the next reader
|
||||
will meet it.
|
||||
|
||||
### Building it and opening it
|
||||
|
||||
The raylib archive is built once and is not in the tree. From the repository root:
|
||||
|
||||
```sh
|
||||
sh vendor/raylib/build-web.sh # clones raylib 5.5 and compiles it with emcc
|
||||
export FLAN_RAYLIB_WEB=$PWD/vendor/raylib/web/libraylib-5.5.a
|
||||
```
|
||||
|
||||
`build-web.sh` prints that `export` line itself. Then:
|
||||
|
||||
```sh
|
||||
flan build sand.flan --target=web -o sand.html
|
||||
```
|
||||
|
||||
**The output must be named `.html`.** `--shell-file` is passed only when it is, because emcc accepts and ignores it
|
||||
otherwise — so `-o sand` produces a module with no shell, no canvas, and a page that looks like it built fine and
|
||||
paints nothing. Three files land beside it, in whatever directory `-o` names: `sand.html`, `sand.js`, `sand.wasm`.
|
||||
|
||||
**A `file://` URL will not work.** The page fetches `sand.wasm`, and a browser refuses that from the filesystem. Serve
|
||||
the directory holding the three files:
|
||||
|
||||
```sh
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
|
||||
and open **`http://localhost:8000/sand.html`**. Left mouse paints; the keys are the ones the native build has.
|
||||
|
||||
### What only a human opening it can settle
|
||||
|
||||
Verified headlessly, by `test/test_web.ml`: the three files exist, the module carries `asyncify_start_unwind` and
|
||||
`glViewport`, and **brush.png's own bytes are in the module, whole** — the assertion that keeps the embed from
|
||||
rotting. Not `IHDR`: stb_image, linked in from raylib, carries that string itself, so an `IHDR` check would pass on a
|
||||
build where the embed emitted nothing.
|
||||
|
||||
Not verified, and not verifiable here. `node sand.js` instantiates the module, runs `main`, and dies inside `glfwInit`
|
||||
on `window is not defined` — which says the module is live and says nothing about the canvas.
|
||||
|
||||
- **Whether it paints at all.** Nothing in CI has ever seen a pixel of this.
|
||||
- **Audio.** `start-audio` generates a tone, `ExportWave`s it to `/tmp/flan-sand-tone.wav` and loads it back as a
|
||||
music stream. That is raylib's own `fopen`, not Flan's `barf`, so the `#ifdef` in `flan_rt.c` does not cover it and
|
||||
emscripten's MEMFS may well give it a writable `/tmp`. Every use is behind `music-ok`/`tone-ok`, so a failure is
|
||||
silence and not a crash. Separately, browsers suspend the audio context until a user gesture, so `audio-ok` may be
|
||||
true while nothing is heard until the first click.
|
||||
- **The loop never exits.** `WindowShouldClose()` on `PLATFORM_WEB` is an `emscripten_sleep` that returns false, so
|
||||
`until` never terminates and **none of `main`'s `defer`s ever run** — no `CloseWindow`, no `UnloadTexture`. That is
|
||||
correct for a page, which is torn down by the tab closing, and it is worth knowing before reading anything into it.
|
||||
- **Canvas size against `screen-width`/`screen-height`.** The shell is a string in `Build` and its canvas is not sized
|
||||
from the program, so 900x600 may be letterboxed or cropped.
|
||||
|
||||
33
NEXT.md
33
NEXT.md
@ -365,6 +365,29 @@ array with a struct element, a 2-D struct array, and `[N string]` as both `defco
|
||||
"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`.**~~ **Built. It opens.** See BUILT.md,
|
||||
"sand.flan in a browser", for the whole of it. Three summary lines, because the diagnosis below was right about the
|
||||
structure and wrong about the cause:
|
||||
|
||||
- The `#include` was never the fix. **The agent is a socket server and a browser has no sockets**, so an agent that
|
||||
compiles there is an agent that can never accept a connection. `vendor/agent/flan_agent.web.c` is three no-ops, and
|
||||
`Build` selects it over `flan_agent.c` on `--target=web` and nowhere else.
|
||||
- **Refusing `vendor:agent` on web was the honest-looking option and is ruled out by arithmetic.** There is no
|
||||
conditional compilation, `sand.flan` calls `agent/start` unconditionally, `Reach` cannot prune a package something
|
||||
reachable calls into — so a refusal means the flagship program does not build for the browser at all. A refusal is
|
||||
only honest when the caller has a way to not ask. This does **not** reverse decision 2 above: `barf`'s no-op loses a
|
||||
file the program believed it wrote, and there is nothing for the agent to lose because `--dev` is already refused by
|
||||
name on every wasm target. The argument is written out at the top of `flan_agent.web.c`.
|
||||
- **A package's `.c` files can now be addressed to a target**, by a tag in the name before the extension, and a tagged
|
||||
file *replaces* the untagged file of the same base name on that target. This is the C-source half of the
|
||||
`@native`/`@wasi`/`@web` link-line mechanism decision 2 pointed at for per-package target isolation.
|
||||
|
||||
The brush is `(embed "brush.png")` decoded through a new `LoadImageFromMemory` binding. `load-texture` and
|
||||
`load-image` now have no call site anywhere in this repository — deliberately, because a path-based load is the one
|
||||
shape the browser cannot have, and said here so it is not read later as an accident.
|
||||
|
||||
The original entry follows.
|
||||
|
||||
**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 ...)`
|
||||
@ -393,6 +416,16 @@ The original text follows. `sand.flan` does `(rl/load-texture "brush.png")` agai
|
||||
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.** *Still true, and now it is the only thing left between here and
|
||||
"someone played with it".* `sand.flan` builds for the web, the module carries asyncify, raylib's GL imports and
|
||||
brush.png's own bytes whole, and `node sand.js` gets as far as `glfwInit` before dying on `window is not defined` —
|
||||
which proves the module is live and proves nothing about the canvas. BUILT.md carries the exact commands to serve and
|
||||
open it, and the list of what only a human will discover: whether it paints, whether the audio round trip through
|
||||
MEMFS survives, and the canvas size. The `until` loop never exits on the web, so none of `main`'s `defer`s run —
|
||||
expected, and worth knowing before reading anything into it.
|
||||
|
||||
The original entry follows.
|
||||
|
||||
**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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user