Skip to content

refactor(libmoq)!: replace the client setters with a config struct - #2880

Open
kixelated wants to merge 2 commits into
devfrom
claude/libmoq-struct-api-stability-d2b998
Open

refactor(libmoq)!: replace the client setters with a config struct#2880
kixelated wants to merge 2 commits into
devfrom
claude/libmoq-struct-api-stability-d2b998

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

  • libmoq ships as a staticlib only: the release tarball packages include/moq.h beside lib/libmoq.a, nix/overlay.nix installs just the archive, and crate-type = ["staticlib"] builds no cdylib. A caller therefore always compiles against the header matching the archive it links, so appending a field to a C struct is a recompile, not an ABI break.
  • The moq_client_set_* / moq_client_get_* functions existed to avoid that break, and the docs said so. Since the break can't happen they bought nothing but surface, so they collapse into one moq_client_config struct plus moq_client_defaults(), which replaces every getter at once.
  • moq_client_connect folds into moq_session_connect, whose new config parameter is NULL for the defaults. One connect function instead of two.
  • Exported C functions: 121 → 81.

Knobs whose default is not zero carry a has_* flag rather than being read directly. websocket.enabled defaults to true and the reconnect backoff to 1s/2/5s/10s, so a caller who zeroed the struct and set nothing would otherwise have silently disabled them. The flag is also what separates "leave it alone" from an explicit value: backoff_timeout_ms = 0 with the flag set means retry forever.

moq_client_defaults returns by value. With no out-pointer there's no handle to look up and nothing to reject, so the int32_t status channel had no failure left to report.

Also in this change:

  • moq_video_config's coded_width/coded_height stop being *const u32. Zero already means absent for a dimension, so each keeps its own presence and a half-specified catalog still round-trips (collapsing them behind one flag invented a zero for the missing half).
  • build.rs sets pragma_once. cbindgen.toml is never loaded, so the generated header had no include guard — fine until moq-settings.h started reaching it down a second include path.
  • moq_publish_{video,audio}_config no longer claim that re-declaring a rendition name replaces it. hang's insert returns Duplicate; pre-existing doc bug.
  • The docs drop their claims that a dynamic library ships (libmoq.so / libmoq.dylib / moq.dll never existed).

Public API changes

Breaking, hence dev — all in rs/libmoq's C ABI:

  • Removed: moq_client_create, moq_client_close, moq_client_connect, and every moq_client_set_* / moq_client_get_*.
  • Added: moq_client_config (struct), moq_client_defaults() (returns it by value).
  • Signature changed: moq_session_connect gains const moq_client_config *config after url_len.
  • Field types changed: moq_video_config.coded_width / .coded_height are uint32_t (were const uint32_t *).
  • Header: moq.h now carries #pragma once.

Rust-side libmoq::Error::ClientNotFound is gone (the handle it named no longer exists). libmoq is a staticlib, so its Rust surface has no consumers.

Test plan

  • cargo nextest run -p libmoq64/64 pass, including a new a_half_specified_coded_size_round_trips that forwards a width-only catalog between broadcasts, and a_zeroed_config_is_the_defaults / defaults_report_what_a_zeroed_config_dials pinned against client::Config::default() so retuning a default without following through to C fails here.
  • cargo clippy -p libmoq --all-targets and cargo fmt — clean.
  • cargo doc -p libmoq --no-deps — clean (several intra-doc links pointed at the removed functions).
  • A standalone TU compiles moq.h twice and exercises every shape cpp/obs depends on: the 7-arg dial with NULL, scalar dimensions, and the moq_client_config fields the settings builder assigns.

Cross-Package Sync

rs/libmoq C ABI → cpp/obs/src and doc/lib/c are both updated. cpp/obs/src/moq-settings.cpp drops ~130 lines of setter calls for field assignments; MoQSettings::Config now owns the strings the config borrows, since libmoq reads them during the dial rather than copying at set time.

Reviewer notes

  • cpp/obs is not compiled here. PR CI never builds it and there's no libobs on this machine, so just obs build / just obs test are still the real gate. An adversarial review pass caught that three OBS call sites hadn't been migrated at all; those are fixed, but the plugin has only been checked against the header, not built. feat(obs): compile the plugin in CI, and type-check it without obs-deps #2867 would close this gap.
  • Per-field validation moved to dial time. A bad fingerprint used to fail at its setter; it now fails at moq_session_connect with the reason in moq_error(). BuildConfig can no longer name the offending field — it still refuses to start, just less specifically. That's the one thing the setters genuinely bought.
  • Overlaps heavily with feat!(libmoq): add an optional track name to moq_publish_media #2831, which touches the same api.rs / test.rs / OBS files. Whichever lands second will need a rebase.

(written by Opus 5)

libmoq ships as a staticlib: the release tarball packages include/moq.h
beside lib/libmoq.a, nix/overlay.nix installs only the archive, and no
cdylib is built anywhere. A caller therefore always compiles against the
header matching the archive it links, so appending a field to a C struct
is a recompile rather than an ABI break.

The moq_client_set_* / moq_client_get_* functions existed to avoid that
break, and the docs said so. Since the break cannot happen they bought
nothing but surface, so they collapse to one moq_client_config struct plus
moq_client_defaults(), which replaces every getter at once.
moq_client_connect folds into moq_session_connect, whose new config
parameter is NULL for the defaults, leaving one connect function.

The knobs whose default is not zero carry a has_* flag rather than being
read directly. websocket.enabled defaults to true and the reconnect backoff
to 1s/2/5s/10s, so a caller who zeroed the struct and set nothing would
otherwise have silently disabled them.

moq_client_defaults returns by value: with no out-pointer there is no
handle to look up and nothing to reject, so the int32_t status channel had
no failure left to report.

Also in this change:

- moq_video_config's coded_width/coded_height stop being *const u32. Zero
  already means absent for a dimension, so each keeps its own presence and
  a half-specified catalog still round-trips.
- build.rs sets pragma_once. cbindgen.toml is never loaded, so the header
  had no include guard, and moq-settings.h now reaches it down a second
  include path.
- moq_publish_{video,audio}_config no longer claim that re-declaring a
  rendition name replaces it; hang's insert returns Duplicate.
- The docs drop their claims that a dynamic library ships.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@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: b3fa6e4572

ℹ️ 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 rs/libmoq/src/api.rs
The clang-format gate rejected the hand-edited moq_session_connect stub in
the OBS output test; clang-format now owns its wrapping.

moq_session_connect's `# Safety` block covered only `url` and `user_data`,
even though a non-NULL `config` is dereferenced along with every pointer
the struct selects. It now states that the config must be aligned and
readable, that each inner pointer must be valid for its paired length, and
that all of them must outlive the call, since the config is read during the
dial rather than copied up front.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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