Repository navigation
Hand metadata and group text to consumers as string_view - #260
Merged
Merged
Conversation
TrackMetadata and on_track_metadata() deliver a metadata state with its text as std::string_view into the role's current text block, and GroupState, on_group_state() and group_state() deliver the group state as views into an inline copy the main loop keeps. The library calls the new callback first and then the deprecated one, skipping the deprecated call after a re-entrant stop(), so existing consumers see no change. ServerMetadataStateObject, on_metadata(), GroupUpdateObject, on_group_update() and get_group_state() are deprecated and go in v0.11.0, together with the ReservedText strings that back them. The examples move to the new API, and the integration guide maps each old name to its replacement.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Metadata and group-state callbacks can detach their listeners without stopping, causing subsequent delivery paths to dereference null listeners.
5 open issues
What changed in this PR
Begins the public text-API migration to std::string_view, exposing existing fixed storage for metadata and group text while retaining deprecated string-based APIs until v0.11.0.
Changes:
- Adds
TrackMetadata,GroupState, new callbacks, and thegroup_state()getter. - Delivers view callbacks before compatibility callbacks, with teardown guards.
- Migrates examples and documentation, and extends content, lifecycle, and allocation tests.
| File | Description |
|---|---|
tests/test_encrypted_lifecycle.cpp |
Tests content parity, view storage, callback order, and progress getters. |
tests/test_client_lifecycle.cpp |
Migrates lifecycle tests and checks both group-state forms reset. |
tests/test_allocations.cpp |
Checks allocation behavior with both callback forms enabled. |
tests/lifecycle_test_fixtures.h |
Migrates the shared metadata listener. |
tests/CMakeLists.txt |
Documents updated white-box test access. |
src/teardown_tracker.h |
Exposes the completed teardown generation. |
src/protocol_messages.h |
Updates payload comments for view-backed public types. |
src/metadata_role.cpp |
Builds metadata views, delivers both forms, and updates progress reads. |
src/metadata_role_impl.h |
Declares view helpers and clarifies current versus compatibility state. |
src/client.cpp |
Maintains inline group state and dispatches both callback forms. |
include/sendspin/types.h |
Adds GroupState and deprecates GroupUpdateObject. |
include/sendspin/metadata_role.h |
Adds TrackMetadata and its callback; deprecates string forms. |
include/sendspin/config.h |
Updates metadata configuration comments. |
include/sendspin/client.h |
Adds the group view API and documents its lifetime. |
examples/tui_client/tui.cpp |
Polls group state through the new getter. |
examples/tui_client/main.cpp |
Migrates metadata and group callbacks. |
examples/basic_client/main.cpp |
Prints metadata views using explicit lengths. |
docs/internals.md |
Describes view delivery, storage, and teardown handling. |
docs/integration-guide.md |
Documents migration, view lifetimes, and deprecated replacements. |
docs/conventions.md |
Establishes the convention for view-backed public text. |
Review effort: Max
Every set_listener() and provider setter now says to set it before start() and leave it in place while the client runs, and the integration guide's wiring step says replacing or clearing one while running is not supported. The deprecated-API section describes the view and string forms in the present tense and notes that a view is not guaranteed to be NUL-terminated.
kahrendt
enabled auto-merge (squash)
November 11, 2026 17:30
kahrendt
added a commit
that referenced
this pull request
Nov 11, 2026
… copies (#261) ## Summary Second step of moving the public API from `std::string` to `std::string_view` (after #260). The pairing callbacks built a `std::string` per call, and the pairing token and server information getters built new strings on every call, all through `operator new`, which aborts on ESPHome. The new forms hand over views or a fixed-size array; the string forms are deprecated and go in v0.11.0. Part of Sendspin/backlog#92. **Migration:** | Deprecated | Replacement | |---|---| | `on_pairing_started(const std::string&)` | `on_pairing_attempt(std::string_view)` | | `on_pairing_succeeded(const std::string&)` | `on_paired(std::string_view)` | | `on_pairing_failed(const std::string&, reason)` | `on_pairing_aborted(std::string_view, reason)` | | `on_display_pairing_code(const std::string&, format)` | `on_pairing_code(std::string_view, format)` | | `pairing_token()`, `format_pairing_token(psk)` | `client_pairing_token()`, `pairing_token_for(psk)`: `std::optional<std::array<char, PAIRING_TOKEN_LENGTH>>` | | `get_server_information()` (`ServerInformationObject`) | `server_information()` (`ServerInformation`), main loop only | Callback views are valid for the callback; `server_information()`'s until control next returns to `loop()` or `stop()`, like `group_state()`. It can trail `is_connected()` by one `loop()`, so read it on each `loop()` rather than only when `is_connected()` turns true. Code off the main loop has no replacement for `get_server_information()` and keeps calling it until v0.11.0. The token array carries no NUL. Overriding a deprecated pairing callback draws no deprecation warning (their parameter types are not deprecated), so search for the four old names, and mark overrides `override` so their removal in v0.11.0 fails to compile. ## Change - **Dispatch:** each new pairing callback fires first, then its deprecated form, which is skipped after a re-entrant `stop()`. The deprecated forms fill a string reserved at construction rather than building one per call, so they no longer allocate either. - **Server information:** `ConnectionManager` publishes the primary's server through a new Inbox slot only when the primary changes (about 115 bytes then, nothing per `loop()`); `drain_inbox()` takes it before any callback, and `stop()` clears the copy. - **Tokens:** `PAIRING_TOKEN_LENGTH` (107) is a public constant, checked against the internal encoder's size. - A scoped pragma in `client.h` wraps the deprecated `get_server_information()` declaration: GCC otherwise warned in every file including the header, because the declaration names the deprecated type. - The examples and the integration guide (pairing sections, Querying State, the Deprecated String API table) use the new API. ## Tests - `ClientLifecycle.PairingCallbacksReachBothForms`: only new, only deprecated, both (new first) and a `stop()` from the new form, across every abort reason and both code formats; the pairing state-machine suite checks every text callback is followed by its deprecated form with the same content. - Token getters: `nullopt` before `start()`, equal to the encoder over the client's identity (configured and stored PSK) and to the deprecated getters. - `server_information()` matches the deprecated getter across restart, re-handshake, pairing, outbound, a liveness drop and a two-server handoff, and is cleared by `stop()`. - Alloc windows: the `pairing_psk` and code-pairing windows (digits and QR) drop from 2, 2 and 3 `operator new` calls to 0 with listeners overriding both forms; admission and the playback handoff cover the server-information refresh at 0. Mutants caught include a skipped or reordered deprecated call, the `stop()` skip removed, views into the deprecated string, an unreserved compat string and a copy never refreshed or cleared. - Builds checked: `-DENABLE_WERROR=ON` with examples, and an ESP-IDF esp32s3 build with no deprecation warnings. Protocol task stack bound unchanged (6,512 B at -Os).
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
First step of moving the public API from
std::stringtostd::string_view. Metadata and group text already live in fixed storage (#256, #258); the public structs still copied it intostd::strings kept allocation-free only byReservedText(about 3 KB of internal RAM for metadata). The view forms hand consumers that storage directly; the string forms are deprecated and go in v0.11.0 withReservedText. Part of Sendspin/backlog#92.Migration: override
on_track_metadata(const TrackMetadata&)instead ofon_metadata(),on_group_state(const GroupState&)instead ofon_group_update(), and callgroup_state()instead ofget_group_state(). Text fields becomestd::optional<std::string_view>: copy them if you keep them past the callback (or, forgroup_state(), past the next return toloop()orstop()). A view is not guaranteed to be NUL-terminated, so print it with%.*sor copy it before handing it to a C-string API. docs/integration-guide.md "Deprecated String API" has the full table.Change
TrackMetadataviews the role's current text block, valid for the callback.GroupStateserves both the deltaon_group_state()receives (the fields changed by the group/update messages since the last call) and the merged stategroup_state()returns. Both view an inline copy the main loop keeps, so the views outlive the string forms' removal.stop()inside the new callback skips the deprecated call (group: the drain generation; metadata:TeardownTracker::completed(), since the protocol task may bump the cleanup generation meanwhile).@deprecatedand[[deprecated("...; removed in v0.11.0")]]:ServerMetadataStateObject,on_metadata(),GroupUpdateObject,on_group_update(),get_group_state(). A consumer of only the new API compiles warning-free; the library silences the warning only at its own compat sites.get_track_progress_ms()/get_track_duration_ms()read the role's current state, so the removal can delete the deprecated object outright. The examples use the new API.Tests
stop()from the new callback, and a protocol-task teardown during it (pinscompleted()overaccepts()). The progress getters are pinned with and without a listener.-DENABLE_WERROR=ONwith examples,-DSENDSPIN_ENABLE_METADATA=OFF, and an ESP-IDF esp32s3 build with no deprecation warnings from the library.Next in the series: the pairing listener strings, server information and token getters; then the vector callbacks, the persistence provider and the configs.