dasGLFW chain dispatcher + synth input; glfw_live mouse driver; APNG recorder#2630
Merged
Conversation
…ommands C++ encoder in modules/dasStbImage/src/apng_write_impl.h reuses stb's internal CRC32 + zlib_compress, runs encode + file I/O on a worker thread with a bounded queue (drop-oldest on overflow). Backpatches acTL.num_frames on close as a single 12-byte write (body + recomputed CRC). Exposes stbi_apng_begin/frame/end/dropped to daslang. Live commands live next to screenshot in opengl_live.das: record_start (file/fps/max_seconds), record_stop, record_status. Capture runs from [before_update] at the fps throttle with auto-stop on max_seconds. dastest covers chunk parse + acTL backpatch, frame 0 decode via stbi_load (default-image fallback), RGB+RGBA, error paths, overflow drops, parallel writers — 12 cases green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dasGLFW.main.cpp gains a multi-listener callback chain for cursor pos, mouse button, and scroll. The dispatcher captures whatever prior C callback was installed (ImGui_ImplGlfw, app-level callbacks) and replays it as the chain's "prev", so synth events posted via DasGlfw_Post* / Dispatch* reach every real listener too. New publics: glfw_chain_add_cursor_pos / mouse_button / scroll, glfw_chain_clear, glfw_post_cursor_pos / mouse_button / scroll, glfw_dispatch_cursor_pos. glfw_live grows a synthetic mouse driver with live commands: mouse_pos, mouse_click, mouse_scroll, mouse_move_to, mouse_play, mouse_stop, mouse_status. Per-frame [before_update] tick lerps between move waypoints and posts one cursor event per frame, so ImGui and overlays see smooth motion. get_synth_cursor() exposes (active,x,y) for overlays to draw against the synth position instead of GetMousePos() -- ImGui_ImplGlfw's per-frame poll would otherwise overwrite io.MousePos with the real OS cursor on focused windows. The OS cursor is never warped: it lives outside the GL back buffer (the window manager paints it at display time), so warping has no effect on glReadPixels-driven APNG recordings. daslib/json_boost.das: PERF018 fix -- from_JV array form now iterates the array directly instead of via range(length). docs: register stbi_apng_* in das2rst stbimage groups + fill the four handmade stubs the apng commit left behind; register the synthesized finalize() in strudel_scheduler's lifecycle group; document the new glfw_live mouse commands in daslang_live.rst + skills/daslang_live.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a streaming APNG recording pipeline and synthetic input support across the dasStbImage/dasOpenGL/dasGLFW stack, with tests and documentation updates to make the new live tooling discoverable and verifiable.
Changes:
- Introduces a threaded, bounded-queue APNG writer in
dasStbImagewith newstbi_apng_*C bindings plus comprehensive APNG tests. - Adds
record_*live commands toopengl_liveand a synthetic mouse timeline driver toglfw_live, powered by a new GLFW callback chain dispatcher and synthetic event posting/dispatch APIs. - Updates docs and reflections (
das2rst) to surface the new APIs/commands and fills previously-stubbed function docs.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
tests/stbimage/test_apng.das |
Adds APNG encoder/parser/decoder tests including overflow/drop and parallel writers. |
skills/daslang_live.md |
Documents new glfw_live synthetic mouse commands and opengl_live recording commands. |
modules/dasStbImage/src/dasStbImage.h |
Declares stbi_apng_* C API for external use. |
modules/dasStbImage/src/dasStbImage.cpp |
Binds stbi_apng_* into the DaScript module. |
modules/dasStbImage/src/dasStbImage_impl.cpp |
Includes APNG implementation under stb write implementation TU. |
modules/dasStbImage/src/apng_write_impl.h |
Implements streaming APNG writer (threaded worker, bounded queue, backpatch). |
modules/dasOpenGL/opengl/opengl_live.das |
Adds record_start/stop/status and [before_update] capture hook. |
modules/dasGlfw/src/dasGLFW.main.cpp |
Adds chained GLFW dispatchers + synthetic post/dispatch API and bindings. |
modules/dasGlfw/src/aot_dasGLFW.h |
Exposes new chain/synth APIs to AOT. |
modules/dasGlfw/dasglfw/glfw_live.das |
Adds synthetic mouse driver + live commands + [before_update] tick. |
doc/source/stdlib/handmade/function-stbimage-stbi_apng_frame-0xfc4bb124cd4cfe02.rst |
Documents stbi_apng_frame (currently mismatched vs implementation). |
doc/source/stdlib/handmade/function-stbimage-stbi_apng_end-0x7f5a70e4959686ea.rst |
Documents stbi_apng_end. |
doc/source/stdlib/handmade/function-stbimage-stbi_apng_dropped-0x45f80abb42af48d6.rst |
Documents stbi_apng_dropped. |
doc/source/stdlib/handmade/function-stbimage-stbi_apng_begin-0xbc1c781f9124134e.rst |
Documents stbi_apng_begin. |
doc/source/reference/utils/daslang_live.rst |
Adds synthetic mouse driver section and updates helper module description. |
doc/reflections/das2rst.das |
Registers APNG writer group and finalize() in scheduler lifecycle docs. |
daslib/json_boost.das |
Improves from_JV array deserialization iteration (perf). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
#1 DasGlfw_ChainClear restores prev callbacks before erasing the map entry. Without this, the dispatcher stays installed but early-returns on every real event, silently disconnecting ImGui_ImplGlfw and any other prior listener. #2 ApngWriter::enqueue validates stride_bytes >= row_bytes and rejects null pixels. Negative or too-small stride was being cast to size_t and underflowing into OOB reads. #3 stbi_apng_frame docstring now matches the implementation: positive stride only, always returns 1 on success, drops oldest on overflow (visible via stbi_apng_dropped). The prior copy claimed negative stride and a 0-return-on-full contract, neither of which exists. #4 mouse_click validates `action` and returns a structured error on anything other than "press" / "release". Typos like "pressed" no longer silently flip to release. #5 mouse_play sorts the wire timeline by t_ms before building the internal queue, so out-of-order input no longer drops earlier events. Extracted MouseEventWire + sort_play_events into a small standalone module (live/mouse_events) so the regression test under tests/dasglfw/test_mouse_play_sort.das can require only the algorithm, not glfw_live's full GLFW/OpenGL chain. The module is marked options no_aot to keep the test interpreter-friendly under test_aot. tests/aot/CMakeLists.txt registers tests/dasglfw/ alongside the other test directories. #6 record_start clamps `fps` to >= 1.0 and stores/reports the effective value, instead of accepting a sub-1 fps in status output while running the scheduler at the clamped rate. #7 record_tick resyncs next_capture_t to `now + frame_interval_s` after a stall instead of catch-up bursting. delay_ms now reflects the actual elapsed time between captures so playback timing matches the real spacing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI: revert the mouse_events module extraction from round 1. daspkg's
exe build treats live/glfw_live as a shared module
(dasModuleGlfw.shared_module) and rejected the require of a
native-path-only mouse_events ("error[20115]: Shared module glfw_live
has incorrect dependency type. Can't require mouse_events because its
not shared"). Put MouseEventWire + sort_play_events back inline in
glfw_live.das; drop the mouse_events.das file, the .das_module
register_native_path, and the tests/dasglfw entries in
tests/aot/CMakeLists.txt. The regression test under tests/dasglfw/
is now self-contained -- it mirrors the wire-event shape with a local
struct and verifies the same sort + less-than comparator lands
t_ms-ascending, so no glfw_live require chain is needed.
#8 mouse_play button branch validates action in {"press", "release"}
the same way mouse_click does. Invalid strings return a structured
error instead of silently flipping to release.
#9 dropped the "Stable-sort" claim from sort_play_events' docstring
and from the test -- daslang's sort is qsort, not stable. The test
now uses distinct t_ms values (no ties), asserting only the
non-decreasing-t_ms invariant mouse_play depends on.
#10 record_tick now deletes the per-frame pixels buffer at end of
function. Local var array doesn't finalize on scope exit, so the
heap was growing by width*height*4 bytes per captured frame.
#11 stbi_apng_frame docstring now states the writer expects bottom-up
row order (glReadPixels output) and flips internally, so callers
don't double-flip.
#12 write_chunk + the acTL backpatch fold the chunk type + body into
an incremental byte-table CRC32 instead of memcpy'ing the whole body
into a temporary vector. Removes the per-chunk peak-memory doubling.
12/12 APNG tests pass (CRC sanity verified through chunk-parse round
trip).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#13 ApngWriter::end now rejects zero-frame closes. Previously it would emit IHDR/acTL/IEND with no IDAT/fdAT -- not a valid PNG/APNG. Stores filepath in begin(), and on end() with frames_written == 0 closes the file, calls remove() on the partial artifact, and returns failure so the caller doesn't ship a corrupt file. #14/#16/#17 test_apng.das: every stbi_load result is now followed by a success(loaded != null, ...) assertion, and the dimension checks (equal x/y) only fire when the load succeeded. Locals x/y/comp are initialized to 0 so a load failure doesn't read uninitialized memory in subsequent (skipped) checks. Applied to test_apng_basic and test_apng_double_writers' l1/l2 paths. #15 test_apng_drop_accounting (renamed from test_apng_drop_on_overflow) replaces the tautological dropped_before_end >= 0 assertion with the real submitted = emitted + dropped invariant: dropped_before_end + chunks.n_fctl == 40. drop_count is only mutated by enqueue (finalized once all 40 submits complete), and end() drains the worker queue, so the invariant is race-free. Works whether the worker was fast (few drops, lots of fcTL) or slow (lots of drops, few fcTL). #18 stbi_apng_frame docstring now notes that 0 can also indicate the writer entered an internal error state from a prior async I/O or encode failure, and that callers should stop and call stbi_apng_end in that case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#19 ApngWriter::enqueue guards size_t overflow on row_bytes * h. Realistic 32-bit hit (windows-32 CI lane); on 64-bit only kicks in for gigapixel frames. One-line `if (h && row_bytes > SIZE_MAX/h) return false;` before the resize. #20 ApngWriter::emit_frame guards the deflate-input multiply ((row_bytes + 1) * h) and the implicit narrowing to int when calling stbi_zlib_compress (which takes int len). Same 32-bit motivation; the INT_MAX cap also matters when zlib API only sees a 31-bit length. Pulled in <climits> for INT_MAX. #21 daslang_live.rst regression -- the apng commit had updated the live/opengl_live row to "OpenGL screenshot + APNG video recording commands.", but the round-1 synth-mouse commit silently clobbered it back to "OpenGL screenshot command." during the stash juggling for the Sphinx-baseline test. Restored. Copilot caught it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
pull Bot
pushed a commit
to forksnd/daScript
that referenced
this pull request
May 13, 2026
Symmetric to the mouse-side synth chain that shipped on master 2026-05-12 in PR GaijinEntertainment#2630. The dasImgui visual-aids tour needs to drive synthetic typing through the same path real GLFW input takes, so any chain listener (ImGui_ImplGlfw_KeyCallback / _CharCallback, app-level handlers) sees the events. dasGLFW C++ (parallels the existing mouse plumbing at lines 160-303): * GlfwChainState gains `prev_key` / `prev_char` GLFW callback slots and `key_chain` / `char_chain` listener vectors. * DasGlfw_ChainKeyDispatch(w, key, sc, action, mods) and DasGlfw_ChainCharDispatch(w, codepoint): call prev_*, then iterate the chain vectors. Mirror DasGlfw_ChainMouseButtonDispatch / DasGlfw_ChainScrollDispatch exactly. * ensure_chain_key_installed / ensure_chain_char_installed: idempotent installer that captures the pre-existing C-callback (ImGui_ImplGlfw's) as prev_*. * DasGlfw_ChainAddKey / DasGlfw_ChainAddChar: call ensure, push to the listener vector. * DasGlfw_PostKey / DasGlfw_PostChar: synthetic-event entry points; call ensure + dispatch. No equivalent of glfwSetCursorPos for keys — GLFW keyboard state is event-driven, not polled, so no warp needed. * DasGlfw_ChainClear extended to restore prev_key / prev_char before erasing the map entry. * addExtern wires daslang-facing symbols: glfw_chain_add_key, glfw_chain_add_char, glfw_post_key, glfw_post_char. * aot_dasGLFW.h gains the four matching DAS_MOD_API decls. dasglfw/glfw_live.das (parallels the existing mouse timeline section): * KEKind enum (press / release / char_), KeyEvent struct. * Timeline state: key_timeline, key_play_start_t, key_play_idx, key_playing. * Public state for visual_aids: synth_held_keys : table<int>, synth_last_keycode + _t, synth_last_codepoint + _t. * synth_key_press / synth_key_release / synth_char helpers that go through glfw_post_key / glfw_post_char respectively and update the public state. * get_synth_keys() accessor for HUD/overlay use. * [live_command]s mirroring the mouse shape: - key_press / key_release / key_char (one-shot) - key_type ({text, total_duration_s? | per_char_ms?}) — ASCII shift table (needs_shift_ascii / base_keycode_ascii) auto-emits Shift around capitals + shifted punctuation; total_duration_s overrides per_char_ms when both present. - key_chord ({mods : array<string>, key : string}) — Ctrl/Shift/Alt/ Super/Cmd/Meta supported; tight 8ms stamps so ImGui sees it as a combo. - key_play ({events}) — mirror of mouse_play timeline format. - key_stop / key_status — mirror of mouse_stop / mouse_status. * [before_update] key_tick drains the timeline frame-by-frame; same shape as mouse_tick. No new tests on this side — coverage lives in modules/dasImgui/tests/integration/{test_glfw_synth_keys, test_visual_aids_key_hud, test_visual_aids_focus_rect}.das (separate dasImgui repo, branch bbatkin/dasimgui-keyboard-synth at github.com/borisbat/dasImgui). 44/44 dasImgui integration tests pass under the interpreter after this lands, including the pre-existing mouse regression at test_glfw_synth_mouse.das (5 cases unchanged).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two commits, one PR:
1. APNG streaming writer +
record_*live commands (apng:commit)modules/dasStbImage/src/apng_write_impl.hreuses stb's CRC32 + zlib_compress, runs encode + file I/O on a worker thread with a bounded queue (drop-oldest on overflow). BackpatchesacTL.num_frameson close as a single 12-byte write.stbi_apng_begin/stbi_apng_frame/stbi_apng_end/stbi_apng_dropped.modules/dasOpenGL/opengl/opengl_live.das:record_start(file/fps/max_seconds),record_stop,record_status. Capture runs from[before_update]at the fps throttle with auto-stop onmax_seconds.tests/stbimage/test_apng.dascovers chunk parse + acTL backpatch, frame 0 decode viastbi_load(default-image fallback), RGB+RGBA, error paths, overflow drops, parallel writers — 12 cases.2. dasGLFW chain dispatcher + synthetic input API; glfw_live mouse driver (new commit)
modules/dasGlfw/src/dasGLFW.main.cppgains a multi-listener callback chain for cursor pos, mouse button, and scroll. The dispatcher captures whatever prior C callback was installed (ImGui_ImplGlfw, app-level callbacks) and replays it as the chain'sprev, so synth events posted viaDasGlfw_Post*/Dispatch*reach every real listener too.glfw_chain_add_cursor_pos/mouse_button/scroll,glfw_chain_clear,glfw_post_cursor_pos/mouse_button/scroll,glfw_dispatch_cursor_pos.modules/dasGlfw/dasglfw/glfw_live.dasgrows a synthetic mouse driver with live commands:mouse_pos,mouse_click,mouse_scroll,mouse_move_to,mouse_play,mouse_stop,mouse_status. Per-frame[before_update]tick lerps between move waypoints and posts one cursor event per frame, so ImGui and overlays see smooth motion.get_synth_cursor()exposes(active, x, y)for overlays to draw against the synth position instead ofGetMousePos()—ImGui_ImplGlfw's per-frame poll would otherwise overwriteio.MousePoswith the real OS cursor on focused windows.glReadPixels-driven APNG recordings.daslib/json_boost.das: PERF018 fix —from_JVarray form now iterates the array directly instead of viarange(length).stbi_apng_*indas2rststbimage groups + fill the four handmade stubs the apng commit left behind; register the synthesizedfinalize()instrudel_scheduler's lifecycle group; document the newglfw_livemouse commands indaslang_live.rst+skills/daslang_live.md.Test plan
mcp__daslang__linton all changed.dasfiles — cleandastest --test tests/— 8026/8026 passtest_aot.exe -use-aot dastest/dastest.das -- --use-aot --test tests— 7420/7420 passdas2rst.dasexits 0, no Uncategorized, no// stubfilessphinx-build -b html—build succeeded.with zero warnings/errorsformat_filereportsalready_formattedon all changed.das🤖 Generated with Claude Code