Skip to content

dasGLFW chain dispatcher + synth input; glfw_live mouse driver; APNG recorder#2630

Merged
borisbat merged 6 commits into
masterfrom
bbatkin/glfw-synth-events
May 12, 2026
Merged

dasGLFW chain dispatcher + synth input; glfw_live mouse driver; APNG recorder#2630
borisbat merged 6 commits into
masterfrom
bbatkin/glfw-synth-events

Conversation

@borisbat
Copy link
Copy Markdown
Collaborator

Summary

Two commits, one PR:

1. APNG streaming writer + record_* live commands (apng: commit)

  • C++ encoder in modules/dasStbImage/src/apng_write_impl.h reuses stb's 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.
  • New C++ bindings: stbi_apng_begin / stbi_apng_frame / stbi_apng_end / stbi_apng_dropped.
  • Live commands in 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 on max_seconds.
  • tests/stbimage/test_apng.das covers chunk parse + acTL backpatch, frame 0 decode via stbi_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.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.
  • modules/dasGlfw/dasglfw/glfw_live.das 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.

Test plan

  • Lint: mcp__daslang__lint on all changed .das files — clean
  • Tests: dastest --test tests/ — 8026/8026 pass
  • AOT: test_aot.exe -use-aot dastest/dastest.das -- --use-aot --test tests — 7420/7420 pass
  • Docs: das2rst.das exits 0, no Uncategorized, no // stub files
  • Sphinx: clean sphinx-build -b htmlbuild succeeded. with zero warnings/errors
  • Format: MCP format_file reports already_formatted on all changed .das

🤖 Generated with Claude Code

borisbat and others added 2 commits May 11, 2026 21:26
…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>
Copilot AI review requested due to automatic review settings May 12, 2026 04:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 dasStbImage with new stbi_apng_* C bindings plus comprehensive APNG tests.
  • Adds record_* live commands to opengl_live and a synthetic mouse timeline driver to glfw_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.

Comment thread modules/dasGlfw/src/dasGLFW.main.cpp Outdated
Comment thread modules/dasStbImage/src/apng_write_impl.h
Comment thread modules/dasGlfw/dasglfw/glfw_live.das
Comment thread modules/dasGlfw/dasglfw/glfw_live.das
Comment thread modules/dasOpenGL/opengl/opengl_live.das
Comment thread modules/dasOpenGL/opengl/opengl_live.das
#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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.

Comment thread modules/dasGlfw/dasglfw/glfw_live.das Outdated
Comment thread modules/dasGlfw/dasglfw/mouse_events.das Outdated
Comment thread modules/dasOpenGL/opengl/opengl_live.das
Comment thread modules/dasStbImage/src/apng_write_impl.h Outdated
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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.

Comment thread modules/dasStbImage/src/apng_write_impl.h
Comment thread tests/stbimage/test_apng.das Outdated
Comment thread tests/stbimage/test_apng.das Outdated
Comment thread tests/stbimage/test_apng.das Outdated
Comment thread tests/stbimage/test_apng.das Outdated
#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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread modules/dasStbImage/src/apng_write_impl.h
Comment thread modules/dasStbImage/src/apng_write_impl.h Outdated
Comment thread doc/source/reference/utils/daslang_live.rst Outdated
#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>
@borisbat borisbat merged commit a5b12f6 into master May 12, 2026
27 of 29 checks passed
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).
@borisbat borisbat deleted the bbatkin/glfw-synth-events branch May 14, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants