Skip to content

ViewBridge: Processor editor lifecycle + VST3/CLAP wiring (Feature 1, Phase 1 + core Phase 2) - #140

Merged
danielraffel merged 4 commits into
mainfrom
viewbridge-v2
Apr 13, 2026
Merged

ViewBridge: Processor editor lifecycle + VST3/CLAP wiring (Feature 1, Phase 1 + core Phase 2)#140
danielraffel merged 4 commits into
mainfrom
viewbridge-v2

Conversation

@danielraffel

Copy link
Copy Markdown
Collaborator

Summary

Adds ViewBridge — editor-view lifecycle and multi-view primitives sitting between Processor and every format adapter. Implements Feature 1 Phase 1 (core API + tests + VST3 + CLAP) and Phase 2 core (secondary views, count/role queries, lifecycle callbacks). Phase 3 adapter-parity for AU/standalone/AAX/WAM and inspector auto-attach are out of scope for this PR and tracked as follow-ups.

Core API (core/format/)

  • Processor::create_view()std::unique_ptr<view::View> (default nullptr, framework falls back to scripted UI / AutoUi)
  • Processor::view_size() returning ViewSize { preferred/min/max w+h } (0 max = unbounded)
  • Lifecycle callbacks: on_view_opened, on_view_closed, on_view_resized
  • ViewBridge class — owns the view, dispatches lifecycle, tracks size hints, supports secondary views via attach_secondary_view(view, ViewRole) / detach_secondary_view / view_count / view_at / role_at

Format adapters wired

  • VST3 (vst3_plug_view.{hpp,cpp}): PulpPlugView owns a ViewBridge bridge_; attached/removed/onSize now route host events through the bridge so Processor::on_view_* fires consistently.
  • CLAP (clap_adapter.hpp, clap_entry.hpp): PulpClapPlugin::bridge replaces raw editor_root + scripted_ui fields; gui_create, gui_destroy, gui_get_size, gui_set_size all route through the bridge.

Tests

  • test/test_view_bridge.cpp — 4 Catch2 cases / 31 assertions covering: AutoUi fallback when create_view returns nullptr, custom view returned by create_view, secondary-view attach/role-query/detach, RAII close-on-destruct.
  • examples/view-bridge-demo/ — headless runnable demo + ctest registration exercising the full open → attach inspector → resize → detach → close flow.
  • Full suite: 2080/2080 pass locally.

Docs + plan

  • docs/guides/view-bridge.md — new public guide
  • docs/reference/modules.md — adds ViewBridge row under format module
  • planning/next-features-plan.md — Feature 1 checkboxes updated for Phase 1 (VST3+CLAP+tests) and Phase 2 (secondary-view API + shared-StateStore binding propagation)

Out of scope (follow-ups)

  • Phase 2: Inspector auto-attach-as-secondary
  • Phase 3: AU v2 (+ dual-Processor bug fix), AU v3, standalone (needs ViewBridge::release_view for TabPanel ownership), AAX, WAM/WCLAP
  • Phase 4: WebSocket remote views (explicitly deferred in plan)

Test plan

  • cmake --build build -j\$(sysctl -n hw.ncpu) clean on macOS ARM64
  • ctest --test-dir build --output-on-failure --exclude-regex AudioWorkgroup → 2080/2080 pass
  • pulp-test-view-bridge → 4/4 Catch2 cases, 31 assertions
  • pulp-view-bridge-demo ctest entry → passes
  • CI: macOS ARM64 + Ubuntu + Windows via Namespace (on PR)

…VST3 wiring

Phase 1 of Feature 1 (ViewBridge).

- core/format/include/pulp/format/processor.hpp: add ViewSize struct,
  virtual create_view() → unique_ptr<view::View> (default nullptr, framework
  falls back to scripted/AutoUi), virtual view_size(), lifecycle callbacks
  on_view_opened / on_view_closed / on_view_resized.
- core/format/src/format.cpp: out-of-line create_view() definition anchors
  the Processor vtable (previously floating, caused link errors once
  create_view became the first virtual in the translation unit). Registered
  in core/format/CMakeLists.txt.
- core/format/{include/pulp/format/view_bridge.hpp,src/view_bridge.cpp}:
  ViewBridge class owns the view tree, calls Processor lifecycle hooks on
  attach/detach, tracks resizes.
- core/format/src/vst3_plug_view.cpp + .hpp: replace raw editor_root_ /
  scripted_ui_ with a ViewBridge member; attach/detach/resize go through
  the bridge so host-thread lifecycle callbacks fire consistently.
- test/test_view_bridge.cpp: 4 test cases / 31 assertions covering headless
  view creation, default AutoUi fallback, lifecycle callback ordering, and
  resize propagation. Registered in test/CMakeLists.txt. Green.
- examples/view-bridge-demo: minimal demo wiring a custom View via
  create_view() and attaching it through ViewBridge.
- docs/guides/view-bridge.md + docs/reference/modules.md updates.

CLAP adapter wiring and multi-view secondary attach remain for Phase 1-2
follow-ups.
Phase 1 CLAP item from Feature 1 (ViewBridge) plan.

- core/format/include/pulp/format/clap_adapter.hpp: PulpClapPlugin now
  owns a unique_ptr<ViewBridge> instead of raw editor_root_ +
  scripted_ui_ fields. editor_host still owns the platform window
  surface.
- core/format/include/pulp/format/clap_entry.hpp: gui_create builds
  the bridge, gui_destroy tears it down, gui_get_size reads size hints
  from the bridge, gui_set_size dispatches to bridge->resize() so
  Processor::on_view_resized fires with the host's new dimensions.

Build clean. ctest: 2080/2080 pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 198b0f2fa3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread core/format/src/view_bridge.cpp
…dex P2 on PR #140)

Codex flagged that Processor::on_view_opened was firing in
ViewBridge::open(), but adapters call open() *before* attaching the view
to the native parent window. Host-window-dependent resources
initialized in on_view_opened could run too early, and if attach later
failed the open/close dispatch would be unbalanced.

- ViewBridge::open() now builds the view only; does not fire on_view_opened.
- ViewBridge::notify_attached() is the new second step — adapters call it
  after successfully attaching to the host parent. Idempotent.
- ViewBridge::close() fires on_view_closed only when notify_attached had
  fired, so failed-attach paths tear down cleanly without a spurious
  on_view_closed.
- ViewBridge::resize() only dispatches on_view_resized while attached.
- VST3 PulpPlugView::attached(): calls bridge_.notify_attached() after
  CPluginView::attached succeeds; cleans up via bridge_.close() on failure.
- CLAP gui_set_parent: calls bridge->notify_attached() only when
  attach_to_parent actually matched a supported window API.
- Tests: two new cases cover deferred dispatch (open→notify_attached
  ordering, idempotent second notify, pre-attach resize is a no-op) and
  failed-attach balance (close without notify_attached fires nothing).
- Demo + guide updated to document the two-step attach protocol.

ctest: 7/7 view-bridge tests green, full suite 2080/2080.
@danielraffel

Copy link
Copy Markdown
Collaborator Author

Codex P2 addressed in b86838d. ViewBridge::open() no longer fires on_view_opened — adapters now call a new bridge.notify_attached() after the host attach step succeeds (VST3 after CPluginView::attached, CLAP inside gui_set_parent on the matched window API). close() fires on_view_closed only if notify_attached had fired, so failed-attach teardowns stay balanced. Two new tests cover deferred dispatch + failed-attach close; all 7 ViewBridge tests green, full suite 2080/2080.

@danielraffel
danielraffel merged commit aea00ab into main Apr 13, 2026
7 checks passed
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.

1 participant