尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗An experimental stack for building games and apps in Haxe, for desktop, mobile and the web, from one npm package. Built on hxmc, the Haxe compiler with a native and WebAssembly target. Unstable: APIs change without notice.
The goal (desktop and web work from this checkout: bun run helder ...,
src/helder/cli/README.md; the rest is on the roadmap):
npm install helder@nightly # main's, published on each push that passes CI
npx helder dev # this machine, with live reload
npx helder dev --target web # a page, with live reload
npx helder build # bin/<target>/<name>, its compile time and size
npx helder inspect --tree # a running dev build's ui tree (--log, --frames)
npx helder devices # where it can run: this machine, a browser, phones
npx helder build --target android # an APK, signed with your debug key (no SDK)
npx helder dev --target android # on the device or emulator adb reaches: installed once, live reloads
npx helder run --target ios # on the booted iOS simulator (macOS)
helder <command> [entry] [options]: the entry (a .hx file, a directory or
a project; the default: .) is what is built, the flags where it runs:
--target (host, web, android, ios, ios-sim or an exact hxmc target), -d (which
phone, emulator or simulator), -o (the output), -- (the program's
arguments). helder <entry> is helder dev <entry>.
helder is all a user installs: it carries the
compiler (hxmc stays an implementation detail, never a second tool to
learn) and the platform tooling behind helder dev and helder build.Main; targets,
SDKs, emulators and signing for development are found or set up by the
CLI. Configuration is for overriding defaults, not for getting started.helder dev.helder dev serves, for other editors) puts the program's assets,
stories, inspectors and profiler beside the code, on every platform, and
shows what the allocation-free core promises: the garbage of every frame.All under src/helder, Haxe package helder.<name>:
os | windows, input, the app loop and audio: X11, Cocoa, Win32, Android, UIKit, the browser |
gfx | a portable GPU API: GL/GLES, Metal, D3D11, WebGPU, WebGL2; shaders written in Haxe |
text | TrueType and CFF fonts in pure Haxe: rasterizing, shaping, bidi, layout, a glyph atlas |
image | PNG, JPEG, GIF and WebP decoded in pure Haxe, and animations' frames |
view | signals and JSX-like markup, compiled to direct calls on the renderer each subtree's tag chooses |
ui | flexbox layout, events, focus and accessibility, drawn on gfx or as the platform's controls |
web | view on the web: DOM and static renderers, hydration, islands |
scene | 3D on gfx: glTF, a PBR renderer, animation, input actions |
asset | files by path: images, fonts, models, sounds, bytes; checked when compiling, bundled by use (the executable, the APK, the app, beside the page), loaded by a coroutine, reloaded in place under helder dev |
physics | a small rigid-body core for scene |
sprite | 2D sprites on gfx: TexturePacker atlases, a batcher of textured quads (moved, scaled, turned, faded, added), <Sprites> in ui's layout |
async | a loop per thread for Haxe 5's coroutines: tasks, timers, TCP, child processes, file watching, an HTTP and WebSocket server; the browser's loop |
jobs | background work awaited from a coroutine: GC-free jobs and GC jobs on a pool's threads or a browser's workers, channels, the sendable check |
dev | the dev host and state kept across a reload |
devtools | the devtools protocol (JSON-RPC on the dev connection) and its agent in dev builds: log, frame stats, the ui tree |
test | tests as bun writes them, without macros: describe, test, hooks, assertions as functions, async tests on async's loop; helder test runs the files |
examples/ has a program per folder; tests/Run.hx has the checks, and
the *.test.hx files are helder.test's, run by the CLI built from this
checkout (bin/cli/helder, rebuilt when its sources change):
bun install
bun run test # the lanes that need no GPU, display or browser
bun run test help # every lane
bun run helder test # the *.test.hx files (--target all: host and wasm, --watch, -t <name>)
bun run helder examples/counter/Counter.hx # an example, live (helder dev; --target web: a page)
bun run site # the examples' page, bin/site (site/), then bun run site:check
The examples' page, https://benmerckx.github.io/helder/, is site/: one
page of every example's card (its header: // title:, // summary:,
// tags:, // demo:), written in TSX through helder's TypeScript frontend
and rendered by helder.web's static renderer (site/index.tsx); those whose
demo: is a canvas or a sound have a Play button starting their wasm32 build
in the page (a WebAssembly island loaded on the click). site/build.mjs
builds each of them for the web, lays out bin/site and runs the page's
program; .github/workflows/pages.yml deploys it on pushes to main.
In order: each phase leans on the one before it. Now: phase 4, a mobile 3D game; the editor integration (phase 3's VS Code extension) and sound wait until after it.
tests/: comments
there still point to docs/, packages/, make and npm test (src/
is done).Done as a first slice:
helder.async (src/helder/async/README.md): a loop per thread for
Haxe 5's coroutines, tasks with structured concurrency and cancellation,
timers, TCP, child processes, an HTTP and WebSocket server natively, the
browser's loop on the web, I/O behind a completion-based backend
(poll(2) natively: sockets, pipes, Linux's pidfds; select on Eval);
processes with a working directory, an environment and signals, the
process's own SIGINT and SIGTERM on the loop; file watching as esbuild polls (SampledWatcher: what the
last build read, the hot files every 100 ms, the rest sampled, 2.3 ms a
tick for 10k files) and a whole-tree PollWatcher.
The helder CLI (src/helder/cli/README.md), a native program on it,
run from this checkout by bun run helder:
helder dev, build and run for the host and web, build for any
hxmc target; one grammar, helder <command> [entry] [options]: the
positional is what is built, never a target (a project, package.json and
src/Main.hx, an example's directory or a file:
helder dev examples/counter/Counter.hx; helder <entry> is dev's), the
flags where it runs (--target web, --target windows-x86_64, -d
for a phone: phase 4), -o, --;
helder devices lists this machine, the browser, adb's devices and
emulators, iOS's simulators;dev.mjs: a failed build keeps the last
good one running; natively each build a library (.so on Linux, a DLL on
Windows, a dylib on macOS) swapped into the dev host's process (the
window and the GPU device or context kept: no new window per edit), a
restart with a snapshot when the host does not build or with
HELDER_DEV_HOST=0; on the web the module swapped in the same page;
save to reload about 300 ms natively, 400-440 ms on the web;helder dev (an exit code of 0, as
tauri dev and electron-forge do); a crash keeps it up, waiting for a
change to build and start it again; closing a page does not (vite's);Still to come:
Assets anywhere, bundled by use: landed with phase 4's assets by
path (helder.asset: below, src/helder/asset/README.md). Still to come:
each in its target's format (compressed textures, phase 5), settings
that differ from the defaults in a sidecar (sword.png.meta);
The dev loop: the dev host on Windows and macOS run on the machines themselves (tested on Linux: cross-compiled, Windows' under Wine with D3D11 on WineD3D, macOS's built only); the page held back while the module is generated; macro-only modules in the watched set.
The dev connection's protocol, the rest: the agent on the web (the
page relaying between the module and the WebSocket; only the log today),
the other domains (Scene, Signals, Profiler, Assets, Stories)
and libraries' own (as Godot's debugger; Agent.method is the hook), get,
query and set on properties (as Bevy's BRP), profiler samples as a binary
stream, node ids kept across reloads, Runtime.evaluate.
Packaging: the CLI on npm as a binary, one optional dependency per OS
and CPU (@helder/<os>-<cpu>, as @hxmc/<os>-<cpu>); name, version and
app id from package.json into the builds; the examples laid out as
projects, each reloaded in CI. helder new and helder doctor wait;
Android and iOS are phase 4.
Tests: the rest of tests/ moved to helder.test, golden images
beside snapshots, --target web (Chromium); editor support for
.test.hx files (the display server takes Foo.test.hx for the module
Foo: no diagnostics, hover or completion); a macro later (@:test
collection, an assert showing its values).
helder.async's backends: io_uring on Linux (poll where it is
disabled: Android apps, some containers), kqueue on macOS, IOCP on
Windows, on hxmc's IoBuffer and @:hxmc.blocking; a process's exit
without polling on macOS (Linux: its pidfd, done); Windows' pipes
without threads; macOS and Windows run.
Renderers as tags in markup, react-three-fiber's <Canvas> model
(src/helder/view/README.md): done as a first slice. No define names a
renderer: a literal is compiled when typed, for the renderer that owns
it (the tag around it, 's parent, a expected
or returned), or for each renderer the program reaches; only those get a
runtime. A component pinned to another renderer, a root in a renderer's
markup, an element a renderer does not list are compile errors at the tag.
Side by side, as they constrain each other. The async design comes from a study of Dart's isolates, Go's channels, Goose, Kotlin's dispatchers, Unity's jobs (Burst) and the Fiberus Haxe target, against hxmc's runtime: one GC heap shared by all threads, collected stop-the-world, where a thread joins a collection only when it allocates or blocks.
helder.async (a loop per
thread, a coroutine never changing thread, structured concurrency and
haxe.coro's cancellation are there):
App.run (done, a first slice:
src/helder/async/README.md, "The loop"): a turn of it in each of the
app's, after the events and before the frame; X11's wait polls the
loop's descriptors and its wake pipe with the connection's, bounded by
its timers (a timer or a job's reply ends an idle App.wait(1) within
0.5 ms: the app lane); Cocoa, Win32, Android and UIKit wait at most 10
ms while I/O or a post may come; the page turns the loop between frames
and while there are none (a hidden canvas: the jobs-web lane). Left:
the OS loops watching the descriptors themselves (CFFileDescriptor,
MsgWaitForMultipleObjects with an event, ALooper_addFd) instead of the
10 ms bound;Fetch (an awaitable one exists, helder.os.Fetch to
follow), the next frame, asset loading.helder.jobs, src/helder/jobs/README.md;
Jobs.run(f, input) awaited from a coroutine, a GC-free job's output an
IoBuffer handed back without a copy natively and transferred on the web,
errors as JobErrors, cancellation through the task; a 4K PNG through
it: frames' max 5.8-6.4 ms and longest pause 0.5-0.6 ms natively, 9.6
and 4.4 ms on wasm32's workers, as with no job; a round trip 28 µs
natively, 45-53 µs on wasm32. Left: channels toward a worker, enums as
sendable values, files in a browser's worker, SharedArrayBuffer as a fast
path):
@:hxmc.unsafe functions over off-heap buffers (hxmc's
IoBuffer), run in a safe region (hxmc.unsafe.Blocking) the
collector never waits for: the default for the engine (decoding,
physics, pathfinding). And ordinary GC jobs for user code: on the web
isolates, natively only for short loops, since a collection waits for
a GC job's longest loop without allocation (35-55 ms for a 4K PNG);Next, ahead of sound and the editor integration (phase 3's VS Code extension, which waits until after the game). The game itself is the driver: built as an example, on a phone, every rough edge it hits the next fix.
Landed: packaging moved from hxmc into helder (helder.pack,
ported with its tests, MIT): app-level and bound to change a lot
(manifests and plists, permissions, orientation, icons, assets bundled
by use, debug and release signing, AAB), so helder's CLI owns it and
hxmc's output ends at native code (Android's libmain.so, iOS's signed
Mach-O, -D hxmc.codesign-* binding the bundle's files). helder build --target android (an APK: manifest, classes.dex when the program needs
it, 16 KB aligned, v2-signed with the user's debug key, now made from
the system's random source on Windows too, under helder's names:
HELDER_ANDROID_KEY, helder's cache), --target ios (an .ipa) and
ios-sim (an .app, ad hoc signed); helder run on a device, emulator
or simulator (-d, else the one there is). Android's runtime objects,
not on npm, fetched on first use from the hxmc release @hxmc/cli's
package.json names (hxmc.rt) into helder's cache, then given to the
compiler as HXMC_RT_DIR. Tested on Linux without a
device (the pack lane: the outputs checked structurally, the v2
signature recomputed and verified by openssl, the Mach-O's signature
slots; helder run and dev against stand-in adb and simctl); on an
emulator and a simulator only in CI, untested until it runs. Next:
hxmc removes its copy. Later: iOS device signing (a certificate and a
profile, through codesign on a Mac), release signing.
Landed: the app's manifest (helder.webmanifest, a W3C web app
manifest with a "helder" member for the native apps' settings;
package.json's "helder" identity its deprecated fallback): one or a
few icons (PNG or SVG) made into every target's at build time: Android's
adaptive icon and Android 12's splash theme in a resources.arsc helder
writes, iOS's app icons and launch image in Info.plist, the web page's
manifest link, icons, theme-color and splash. Later: desktop bundles'
icons (macOS .icns, Windows' resource, Linux's .desktop), an asset
catalog for iOS's dark launch screen and App Store icon.
Landed: helder dev --target android and --target ios: a
connected device or a running emulator (-d, from helder devices),
or a simulator, booted for it when it is not; the dev host's app
installed once, each build swapped into it (Android: its library
fetched by the app over adb reverse and loaded in the same process;
the simulator: a dylib it loads from the Mac's disk), as the desktop's
dev host does. Closing the app on the phone does not end
(helder.cli.ExitPolicy's Mobile: it keeps serving, the next build
installs and starts the app again). Tested against stand-ins for adb and
simctl (tests/pack/dev.mjs); on an emulator only in CI
(tests/os/dev/dev-android.mjs), untested until it runs. Later: booting
an emulator for it.
helder.scene.render.PostProcess: physically based bloom (a chain of
half-sized targets down and up, as Call of Duty: AW and Unity's HDRP),
ACES or AgX, a grade, a vignette, grain; <Scene hdr>.Environment.prefiltered, an atlas of GGX-filtered rows by roughness
and the irradiance, baked ahead; a rim light. Still to come: the
prefilter in helder itself (a pass at load, or the asset step), cubemaps
in gfx.helder.scene.view (a prototype: <Scene> in ui's layout, <Ui>, picking
by bounds; examples/planets, tests/3d-ui):
onFrame
that writes the nodes it is given, and instancing elements with their
per-instance data in flat arrays (particles, crowds, foliage);Vec3) where a
binding expects one (the prototype goes through a signal);helder.2d: sprite batches and tilemaps on gfx and helder.image,
in code and in markup alike.Folded into phase 3: the VS Code extension is the devtools.
helder build. Markup names what it embeds
(<page>...<WasmCanvas entry="examples/cube"/>...</page>, an island);
each such use leaves a marker, found after DCE as helder.asset's are, so
only what is rendered counts; the CLI builds the page (run once for its
HTML: the static renderer writes a canvas and the loader for each), each
embedded program as its own wasm, and the component islands as one bundle
whose main is generated (no Islands.start written by hand), in
parallel through the compilation server; helder dev rebuilds the part
that changed. The examples site (site/) starts with a build per example;
this replaces it. It pays again with native webviews as a surface (a
WKWebView, Android's WebView, WebView2 in a window): web content in a
native app, and native or wasm surfaces inside that, nested the same way.MIT (LICENSE).
*.test.hxcli | the helder command, a native program on async: dev, build, run, test and inspect, for desktop, the web, Android and iOS |
pack | Android's APKs and iOS's app bundles around hxmc's native code, without an SDK: zip, binary XML, resources.arsc (icons, the splash theme), DEX, v2 signing (P-256), the debug key; Info.plist, CodeResources, .ipa |
helder testNO_COLORTERM=dumbFORCE_COLOR"helder" key and a
helder.hxml beside it as the overrides; the app's name, icons,
colours, splash and native ids in helder.webmanifest, a web app
manifest served to browsers as it is, with a "helder" member for
Android's and iOS's own (src/helder/cli/README.md: its icons made for
every target at build time); every example built by
helder build <file> with no flags (the examples lane).helder test and helder.test (src/helder/test/README.md): the
*.test.hx files under src/, tests/ and the class paths, linked into
bin/test/src (Haxe takes no dot in a module's name), one generated main,
built once, run on the host or wasm32-wasi (--target all; Eval only
for the files that opt in, @:test.interp: compile-time code), paths
written back as the real files'; -t, --watch, -u for text
snapshots. The library has no macros: describe, test (.skip(),
.only(), .timeout()), hooks, async(coroutine) bodies, assert,
equals (deep, both values shown), notEquals, approx, throws,
require, fail, snapshot.
One message format on the dev connection
(src/helder/devtools/README.md): JSON-RPC 2.0, versioned by a
Runtime.hello exchange, methods in domains enabled per tool:
Runtime (hello, reload as a request answered once saved, ready,
state: the keys Hot keeps and dropped; the CLI's built, buildError,
reloaded, connected, exited), Log (traces with their positions, the
program's output and the builds' errors, the last 500 kept by the CLI
for a tool that comes late), Frame (time, work, GC bytes, collections
and pauses per frame, 10 batches a second, no garbage per frame:
60 ns enabled, 0.4 ns disabled), Ui.getTree (a hook helder.ui
registers);helder dev as the relay (a WebSocket at
/__helder/devtools on its HTTP port, natively too, named by
bin/dev/devtools.json), helder inspect --tree --log --frames --state;{"type"} lines still spoken to a program its dev.mjs
runs; the devtools lane, headless.hxmc's fixes in a nightly (@hxmc/cli 0.1.0-nightly.20261027.a4195c7),
their workarounds removed: processes' cwd, env and signals and
nanosecond mtimes (#81), sockets' connect and poll (#79, the native
backend), allocation-free IntMap, resize and short sorts (#84, where as
fast), GcStats (#85), Blocking and IoBuffer (#86), a --wait
server kept by an empty connection; WasmGC's lanes removed (hxmc drops
it, #90); haxe.Timer.stamp without the event loop, hxmc's std
overrides trimmed by DCE (#89: the devtools agent and helder.test on
Timer.stamp again; native builds 100 to 210 KB smaller: Todo 2042 to
1849 KB, Planets 2438 to 2234, Counter 1663 to 1499, Walker 708 to 607,
a one-test helder test 297 to 262; wasm32 4 KB smaller); the platform
package's compiler finding its runtime objects beside itself (#93: the
CLI no longer gives HXMC_RT_DIR).
Render.renderViewOf<R><Surface> (UiWindow.run(<Surface ...>)): a window natively, its
props reactive, several for several windows; on the web a canvas
filling the page, or the canvas a page gives it. In a page's markup
(DOM or static) helder.web's <Surface> is a <canvas> the page's
styles size (a ResizeObserver) running a wasm ui program, an island in
a static page.<Scene>: a box in ui's layout drawing scene nodes (its world into
a texture of its size, before ui's pass); ui's pointer events picked in
the scene (bounds) and given to the scene nodes' listeners. <Ui>:
back to ui over a scene node.helder build <file>, natively and for web,
with no flags; the defines, the CLI's and the tests', are gone.Still to come: warm builds of a program with markup still a little slower
than before (every runtime and the late code made by macros each build;
hxmc da5aa2b's #94 took Todo's warm build from 0.76 to 0.55 s, the late
code from the cache waits on hxmc's "one cache" rewrite); late code kept whole by DCE natively (on JavaScript
per literal); <Surface> on phones; <Ui> laid out a frame late and
without its own events past ui's; picking against triangles; the scene's
feature pipelines (skins, normal maps, the sky) in a <Scene>'s target;
<Dom>/<Html> (not needed yet: a page's markup is the DOM's or static
HTML's by where it is mounted); <Page> (static sites keep
Static.document(<html>...), a StaticRenderer literal: a tag adds nothing
until a server renders pages).
TypeScript for apps (optional, after the boundary tags): end users write their app in TS/TSX; helder itself stays Haxe, and the output is hxmc's, native or wasm, with no JavaScript in it.
helder ts frontend (started: helder.typescript)
the CLI runs when a project's entry is src/main.ts(x): the program
checked by the app's TypeScript 7.1+ (tsgo, driven over its API's pipes
from Haxe: no node at build time), its AST lowered straight to hxmc's
IR inside helder's compiler (hxmc's, with this frontend), JavaScript's semantics from a
runtime in Haxe; helder dev keeps tsgo up in the compilation server
and feeds it the watcher's changes; errors and stack traces mapped back
to the .ts lines, as .test.hx paths are. Still to come: the CLI's
entries, helder's API from TS, TSX.<Scene> in TSX once scenes take markup..d.ts files in the npm package, generated from the
Haxe API when helder is published (a .hx content mapper, TypeScript
7.1's plugin point, only if apps mix their own .hx files in).async/await on helder.async, number as Float (an i32 alias
for Int); refused: any spread through code, mapped and conditional
types beyond what Haxe can express, prototypes changed at run time.
Shaders in TS (to Haxe shader classes) later, for games..d.ts for view and ui, the emitter, the counter
and todo examples in TSX built and live-reloaded by helder dev; started
once TypeScript 7.1's API is out of beta.Sendable): flat values, hxmc structs, Strings
and Bytes (copied), and owned buffers (handed over without a copy
natively, transferred on the web). Done for a first slice: Channel<T>
from any thread or job to the loop of the thread that made it;
anonymous structures and Arrays of these are sendable too (copied).PngCore, is @:hxmc.unsafe over native memory, the same code
PngDecoder runs on the calling thread and ImageJobs as a GC-free
job (every colour type, depth, Adam7, APNG's frames; the old decoder
removed: 1.4x faster, a quarter of the garbage, 83 KB smaller). Left:
JPEG, GIF and WebP still decode on the calling thread, and scene's
Images hook is synchronous (textures made as a model loads).Rasterizer's
coverage and an outline's flattening over Raw-like memory, one
implementation for the atlas's warm path and the job; a font's bytes in
an IoBuffer the jobs share read-only (natively one copy, in a browser
one per worker); GlyphAtlas.get of a cold glyph queues it and answers
a fallback (the glyph at a size it has, scaled, or a blank of its
advance), a frame's queue one job (the outlines flattened on the main
thread, cheap), its bitmaps placed when it is back and the atlas's
version bumped, so that a renderer uploads them; TextLayout needs no
pixels (its metrics are the font's). Shaping as a job after it.bun run test bench
(baseline), with whatever storage
measures best. The text pilot (results)
removed its garbage (a warm atlas allocates nothing), but scoped memory
made a cold atlas 6% slower natively and 35% on wasm32; on GC buffers
kept for the next call it is 6% and 8% faster than before, with the same
garbage. Physics (results) allocates
nothing in steady state (from 40-720 KB a step): contacts in reused GC
arrays, struct-of-arrays, no boxed vectors; scoped memory measured within
5% either way, with a larger, slower build, so GC buffers. TextLayout's
output is flat glyph data, read by index, in buffers a layout keeps and
wraps again at another width (results):
ui's full layout of 10k nodes from 11 MB of garbage to 94 bytes, 3.9x
faster natively, 3.1x on wasm32. Next: ui's pointer moves (578 bytes
each: the hover paths' Arrays, the events); flexbox and scene allocate
next to nothing. The GC-facing API of each package stays.helder.scoped: not needed, archived. It lost or tied against GC buffers in
text and physics, and as a GC-free job's memory it lost to off-heap
memory: decoding a 4K PNG took 240 ms against 169, the variant added
149 KB of code against 4, it needed the checker (1.8 s builds against
1.0), its job's freedom from the GC went unchecked, and its pixels could
not cross threads (results). Archived:
removed from the tree, kept under the tag archive/scoped.helder.jobs (above). (Upstream's hxcoro or a runtime of our own: our
own, helder.async, which hxcoro's tasks run on.)helder dev, drawn by helder's ui as wasm (dogfooding), shown in VS
Code's webviews and openable in a browser, so other editors (JetBrains,
Zed, Neovim) get them as pages beside the protocol, helder inspect and
the MCP server. Scenes and ui stay markup in .hx (no scene format of its
own, as Defold's .gui and .collection): stories preview them, and an
inspector's edit writes back to the literal.
helder test's files and results in the test
explorer, a failure's line a click away..hx, .test.hx and .story.hx mapped for the display
server (today they have no diagnostics, hover or completion), jumping to
source from inspector nodes and profiler spans..meta), reloaded live.-D hxmc.dev, as Hot; a few tens of
KB): counters and spans that do not allocate, GC bytes and pauses per
frame, tree snapshots on request, commands, pick and highlight. Its
protocol is there (phase 2: src/helder/devtools/README.md, frame stats,
the log and the ui tree), and helder inspect a small client of it.Foo.story.hx files anywhere (linked as
.test.hx files are) whose stories() registers
story("Inventory/full", c -> ...), built into dev builds only; controls
are signals (c.number, c.bool, a choice, a colour), so an edit
updates only what is bound; the story and its controls kept across a
reload; a story is a test too, rendered headlessly by helder test
against a snapshot (an image, or the tree as text), with a scripted
interaction and assertions..test.hx/.story.hx mapping and markup highlighting; the image
editor (once assets are referenced by path); the console and frame
stats over the protocol. Then the stories editor, the inspector with
jumping to source, the glTF and font editors.helder devLanded: <Surface> on phones: the safe area on both
(Window.safeTop... in pixels: iOS's safeAreaInsets, Android's
window insets through JNI, API 30's getInsets(systemBars | displayCutout) or API 29's system window and cutout insets; a Resize
when they change), in ui as CSS's env(safe-area-inset-*) in markup's
literal attributes (with calc(), max(), min(); bound to
SafeArea.insets, laid out again when they change), Ui.safeArea, and
SceneBox.safeArea() for a scene's HUD; Window.scale (Android's
density, iOS's nativeScale, a ScaleChange when the configuration
changes it); Window.immersive (Android's immersive mode, iOS's status
bar and home indicator hidden); package.json's "orientation"
(portrait, landscape, any: the manifest's screenOrientation, the
plist's UISupportedInterfaceOrientations and UIRequiresFullScreen)
and "fullscreen" (-D helder.fullscreen, UIStatusBarHidden); a
rotation resizes the surface through the existing Resize. Tested: the
options' manifest and plist (App.test.hx, Apk.test.hx,
Bundle.test.hx; the pack lane's PackCheck on the built APK and app:
screenOrientation, the orientations, the define reaching the
program), env() and its binding (SafeArea.test.hx, natively and as
wasm32-wasi); on CI's emulator (a Pixel 6, API 34) and simulator
tests/pack/Screen.hx (tests/pack/screen.mjs): the window's size and
scale (the Pixel 6's 2.625, the iPhone 17's 3), the insets (Android:
the navigation bar's 126 px at the bottom; iOS: the island's 186 at the
top, the home indicator's 102 at the bottom), immersive on Android (the
bars' inset gone, the window into the camera's cutout: its 128 px at the
top), a rotation on Android (wider than high, the cutout at the side).
Not checked anywhere yet: the simulator's rotation (simctl has no
command for it), immersive's effect on iOS's safe area (none on the
island's iPhone), a ui drawn on a phone with the insets applied. Later:
env() in runtime-set attributes and in sheets' rules, the keyboard's
inset kept apart from the bars'.
Landed: the frame loop from the display: Choreographer
(AChoreographer_postFrameCallback64, NDK only, on the program's own
looper: helder.os.android.Vsync) on Android, a CADisplayLink on
iOS (its callback stops the run loop's wait); App.run asks for the
next vsync only while a frame may be drawn, sleeps until it comes, and
draws with App.frame (a FrameClock: the vsync's time in
haxe.Timer.stamp()'s seconds, the delta, the display's period); an
idle frame (App.wait) asks for none until it has slept; the
background, a lost window and an inactive app (onPause, the window
destroyed; willResignActive, didEnterBackground) ask for none, the
first frame after them a period's delta. Tested: FrameClock.test.hx
(natively and as wasm32-wasi: 60 and 120 Hz, a missed frame, a pause,
nothing allocated); on CI's emulator and simulator the fixture's first
frames vsyncs, 120 of them at the display's pace (16.67 ms on both)
with nothing allocated, Suspend and Resume and the first
frame after (the delta a period); desktop and web keep their loops
(App.frame from the turn's start: the gfx, ui-draw, app and browser
lanes). Later: ProMotion's 120 Hz (preferredFrameRateRange,
CADisableMinimumFrameDurationOnPhone), Android's frame rate API.
Multi-touch and gestures, after a study of the platforms' and engines' models (web Pointer Events, Android MotionEvent, UIKit's touches and gesture recognizers, Flutter's gesture arena, Unity's Input System, Godot), in four steps:
os (Pointers: mouse, touch, pen;
dense ids, phases, cancellation, coalesced samples; per event and per
frame, allocating nothing; emulate keeps a touch's old click and
scrolls). Tested natively (no allocation) and in Chromium (fingers,
a pen, the mouse); Android's and iOS's back ends are written and
compile, not run on a phone yet (CI's emulator and simulator jobs
come first).releaseCapture, onPointerCancel, onLostPointerCapture; a tap
clicks, the first finger's drag scrolls in ui itself; a press on
ui's own nodes marks the pointer the ui's), a <Scene> picking at
each pointer's press only (the node hit owns it), one SceneEvent
per box. Tested natively and as wasm32-wasi
(src/helder/ui/Pointers.test.hx), the todo's touch drag, flick and
tap in Chromium (ui-web).Touch(zone),
TouchDelta look zones, VirtualStick floating or fixed,
VirtualButton, a press owned by its control until it lifts, ui's
presses left alone, cancels letting go; <Stick> drawn in ui), read
once a step from Pointers with nothing allocated. Tested natively
and as wasm32-wasi (TouchActions.test.hx), <Stick> under Xvfb.Tap, Pan, Scale; onTap, onPan, onPinch, onScale in
markup; a recogniser that wins takes the click), onTap on scene
nodes and <OrbitControls> for a <Scene>'s camera (one finger
turns it, two pinch about their centre, move and twist it; the mouse
and the wheel too; a glide). Tested natively and as
wasm32-wasi (Gestures.test.hx, OrbitControls.test.hx), a tap and
an orbit over a GPU's <Scene> under Xvfb.Tested where: the desktop (Linux, natively, and its GPU under Xvfb)
and the browser (wasm32-wasi in headless Chromium, touches and pens
through the DevTools protocol); Android's and iOS's back ends are
written and compile, not run on a phone yet. Later:
long press, double tap and waitFor, rotate alone, touch-action on
nodes, pen tilt and pressure in use, prediction, Windows' and X11's
touch screens, macOS's trackpad gestures (magnify, rotate).
Landed: assets by path, bundled into the APK and the app (phase
2's "assets anywhere, bundled by use"; helder.asset,
src/helder/asset/README.md): Asset.image, font, model, sound,
bytes (and embed) by a path anywhere in the project, checked when
compiling (the file there, in that case; a path's rules: AssetPath),
a handle loaded on the main loop (load() a coroutine, ready,
peek(), the handle a node of helder.view's graph, an async memo, its
read() tracked; under helder.view's <Loading>, Solid 2's model, the
content waits on its first load only: its fallback, then the content,
kept through a reload; isPending); Model.of(path, Lazy) is Asset.model,
Model.of reads its file through the same one way. Bundled by use: each
call's marker is in the output only while its code is (hxmc emits the
literals of the code it keeps), so helder build takes exactly those:
desktop's executable has them as resources (read where they are mapped,
no file beside it; a build whose built-in files differ is made again),
the APK's assets/ (stored, read through AAssetManager), the app's
assets/ (sealed by CodeResources before the link), the page's
assets/ (fetched; Model.of's before main). helder dev reads them
from disk (an Android app from helder dev: asset <key>), watches them
and sends a change without a build (Assets.changed): an Image, a
FontFile or a Model's meshes swapped in place, the handle's readers
run again. Tested: AssetPath.test.hx, tests/asset/Assets.test.hx
(natively and as wasm32-wasi: the bundle without a file named only in
dead code; <Loading> over an image in ui's markup), tests/view's
ViewTest (<Loading>: the fallback on a first load only, nested, a
memo's and a component's reads, a failure the Errored's, none
above: nothing rendered; on Eval, natively and as JavaScript), the asset lane (rejections; tests/asset/Checksum.hx alone
in an empty directory, its files' CRC-32s; the web's files exactly), the
pack lane (PackCheck: the APK's, the app's and the .ipa's assets/,
CodeResources' hashes), the cli lane (an image swapped in the same
Image with the dev host and without, a text's reader; the page told),
tests/os/dev/dev-asset.mjs (the page's reload in Chromium),
tests/pack/dev.mjs (the dev APK without assets, asset <key>); on CI's
emulator and simulator Checksum's lines (tests/pack/android.sh, ios.sh;
codesign --verify with assets) and an asset reloaded in the running app
(tests/os/dev/dev-android.mjs), passing there (Pixel 6, API 34; iPhone
17, iOS 26.2). Later:
.meta sidecars, compressed textures, sounds decoded.
Landed, a first slice: the game, Floating Greens
(examples/golf/README.md): mini golf on floating islands, nine holes
built from kit pieces in code (flats, ramps, rails, blocks, bumper
posts, moving blocks, the cup) with a par each; the shot one gesture (a
drag back from the ball: a recogniser of ui's arena winning the press at
once, so <OrbitControls> on the same <Scene> turn and pinch the
camera from anywhere else), the camera following behind the ball and
facing the cup when it stops; helder.physics at 120 Hz, deterministic
(six recorded rounds holed natively and as wasm32-wasi: the examples
lane), a frame of play allocating nothing (GcStats); a trail, a squash,
a shake, slow motion, confetti; the HUD and the holes' menu (stars, best
strokes) in ui on helder.view's graph, clear of the safe areas; the save
in helder.os's Store, procedural sounds through Sfx. Built for the
web (on the examples site, full screen or expanded over the page, its
APK on its card), natively, for Android and the iOS simulator (CI's
emulator and simulator: built, started, its first frames, a
screenshot). On the way: multisampled render targets in helder.gfx on
every back end (<Surface> and <Scene> antialiased by default),
WebGPU's bind groups no longer reusing a recycled handle's destroyed
texture. Still to come: tweens for gameplay, live reload on a phone
tried with it, its gaps (the report's list: an example's app options,
a kinematic body, rolling resistance, struct values in Arrays boxed).
Landed: save data, helder.os.Store (src/helder/os/Store.hx): a
small key-value store of strings, written through to a JSON file in the
app's data directory (Linux's XDG data home, macOS's Application
Support, Windows' APPDATA, Android's files directory, iOS's Library),
localStorage on the web. Tested natively and as wasm32-wasi
(Store.test.hx), the web's through golf's save in Chromium.
Landed: CI on an Android emulator and the iOS simulator: the
android job (a Pixel 6's emulator, API 34: hello through helder run and adb, the screen fixture, Android's dev loop) and the
ios-simulator job (the newest iPhone's simulator: hello through
helder run, the screen fixture), green, not required.
Later in this phase: audio (decoders: WAV, Ogg Vorbis; a mixer above
every back end, running as a kernel; AAudio on Android, RemoteIO on iOS;
a simple Sound.play; a first slice landed: helder.os.audio.Sfx, a
few voices mixing one-shot effects made up front on the existing
outputs, desktop and the web, nothing allocated on the audio thread,
Sfx.test.hx; silent on phones until their back ends), a compositor thread for scrolling and
animations, so a busy main thread does not show (as Ladybird's
compositor, Core Animation, Flutter's raster thread): the main thread
hands ui's renderer a retained layer tree (layers, transforms, clips,
scroll offsets) in flat, off-heap buffers; a render thread, a GC-free job
in a safe region, scrolls, runs transform and opacity animations and
presents at the display's rate; input routed to compositor-scrolled
layers. Layout itself stays on the main thread (10k nodes laid out in
4.2 ms, allocating next to nothing): moved off it only if a measurement
shows layout holding frames back. On the web, workers share no heap: the
layer tree transferred, or a SharedArrayBuffer behind cross-origin
isolation. And devices in the extension: their logs, a screenshot or
a recording, a story picked on the phone.