Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/realtime-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Determine the diff: if `$ARGUMENTS` names a commit range or branch, diff that; o
## Core handler budget

- Every core handler stays within about 200 microseconds: AEAD over one message, one X25519 step, a few KB of JSON. Receive stamps are read on the network thread, so a long handler does not move them, but it holds every time reply and send behind it. Flag new work on core that scales with the number of connections, members, frames or queued items without a bound per handler.
- Work that can exceed the budget is cut into posted steps, as `GroupToCoreFifo` drains (`DRAIN_BUDGET` events per handler, then it posts itself again), or moved off core, as the pipeline jobs are (to the group audio threads, `AudioWorkers`). Flag a loop that drains a whole queue in one handler, and encoding or resampling on core.
- Work that can exceed the budget is cut into posted steps, as `GroupToCoreFifo` drains (`DRAIN_BUDGET` events per run of its `Wakeup`, then it signals itself again), or moved off core, as the pipeline jobs are (to the group audio threads, `AudioWorkers`). Flag a loop that drains a whole queue in one handler, and encoding or resampling on core.
- A pipeline job stays one frame per instance with at most one resampler built, so groups sharing the group audio threads take turns. Flag a job that loops until its input runs out.
- When the verdict depends on a number, say what to measure, in a `RelWithDebInfo` build.

Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ script/clang-tidy.sh - clang-tidy over src/ using build/compile_commands.js
- `host_view.h`: `HostView`, the host side of one input under one mutex: its kind (buffered or live), staging ring and its input segments, runs and epochs, anchoring and the anchor's shifts, drain, stop latch, the group parameters (`GroupParams`, in `group_params.h`) and a live input's latency slew.
- `live_slew.h`: `LiveSlew`, a live input played slightly fast while its raised latency comes down (`sched/lowering.h` decides how fast): micro-resampler's ASRC on a host, single frames dropped on the embedded profile.
- `group_audio.h`: `GroupAudio`, a group's pipelines and visualizer analyses, one job at a time, with the group-to-core FIFO and the pipeline-set slot.
- `payload_slots.h`: `PayloadSlots`, one pipeline instance's frame payloads in fixed slots placed when the instance is built, returned from any thread; `frame_payload.h` holds `FramePayload`, a frame's bytes in one slot.
- `audio_workers.h`: `AudioWorkers`, the group audio threads: a fixed pool that runs each group's pipeline jobs off core, a group's jobs one at a time, groups taking turns.
- `player_stream.h`: `PlayerStream`, one member's player stream: markers, cursor, ledger, chunk packing and the switch to another format in place, over a `StreamSource` (`stream_source.h`).
- `playback_policy.h`: a session's activation (`decide_activation()`), a player's stream format, the group parameters and what a host's move does with a client (`move_action()`), as pure functions.
Expand Down Expand Up @@ -78,7 +79,7 @@ script/clang-tidy.sh - clang-tidy over src/ using build/compile_commands.js
- `inbound_hold.h`: `InboundHold`, a connection's message held for the inbound ring by a transport whose receive task reads for several connections (the ESP-IDF one): who pushes it, the pause tied to it, the close after it; platform-free and host-tested.
- `inbound_ring.h`: `InboundRing`, how a transport's network threads hand received messages, with their receive stamps, and connection events to core: bounded, a message it refuses held back in the transport, independent of any transport.
- `clock.h`: `now_us()`, the clock every timestamp is taken from.
- `profile.h`: the build's resource profile (write-ahead, staging ring, connection caps, the internal RAM a new connection needs, inbound ring, FLAC LPC, a source's clock correction and a live input's slew, audio threads, where each task's stack lives), the one place `SENDSPIN_SERVER_EMBEDDED_PROFILE` changes the library's limits.
- `profile.h`: the build's resource profile (write-ahead, staging ring, a pipeline instance's payload slots, connection caps, the internal RAM a new connection needs, inbound ring, FLAC LPC, a source's clock correction and a live input's slew, audio threads, where each task's stack lives), the one place `SENDSPIN_SERVER_EMBEDDED_PROFILE` changes the library's limits.
- `memory.h`: placed allocation, each naming the memory it prefers (`MemoryLocation`: internal RAM or PSRAM, the other as the fallback): `placed_malloc()`, `PlacedAllocator` and `ExternalBytes` for containers, `make_placed_array()`, `make_shared_external()`, the `ExternalNew` base, and `memory_stats()`; `src/esp/memory_esp.cpp` on ESP-IDF (heap_caps, whatever the project's malloc policy), `memory_host.cpp` on the host (one heap).
- `shared.h`: `SharedPtr`, `WeakPtr` and `EnableSharedFromThis`, the transport's shared pointers: with the atomic lock policy where libstdc++ is built without it (the ESP-IDF riscv32 toolchain), `std::shared_ptr` elsewhere.
- `thread_setup.h`: `start_thread()`, each library thread's name and placement, and `current_thread_token()`, the calling thread's identity from any task; `thread_setup_host.cpp` on the host, `src/esp/thread_setup_esp.cpp` on ESP-IDF (core, priority, and the stack in internal RAM or PSRAM as `profile.h` places it; the token is the FreeRTOS task handle), with the connections' writer tasks.
Expand All @@ -95,7 +96,7 @@ script/clang-tidy.sh - clang-tidy over src/ using build/compile_commands.js
- Library target `sendspin_server_cpp` (alias `sendspin_server_cpp::sendspin_server_cpp`); CMake options are prefixed `SENDSPIN_SERVER_`. `SENDSPIN_SERVER_ENABLE_WERROR` (default off) makes warnings in this project's targets errors; CI builds with it on, so local builds should too. `SENDSPIN_SERVER_EMBEDDED_PROFILE` (default off) builds with `src/profile.h`'s embedded limits, as the ESP-IDF component (`cmake/esp_idf.cmake`) always does.
- Plain: `cmake -B build && cmake --build build`, which builds `examples/` too (`SENDSPIN_SERVER_BUILD_EXAMPLES`, default on when top-level). Uses ccache when installed (`SENDSPIN_SERVER_USE_CCACHE`, default on when top-level). `SENDSPIN_SERVER_ENABLE_DISCOVERY` (default on) builds the mDNS backend: dns_sd on macOS, avahi-client through pkg-config on Linux (`libavahi-client-dev`). `SENDSPIN_SERVER_ENABLE_OPUS` (default on) builds the Opus encoder and the source role's Opus decoder; off compiles them out (`src/frame_encoder.cpp` and `src/source_decoder.cpp` are the places it is guarded) and is refused with the tests on.
- Tests never use the network's mDNS: `tests/test_recorder.h` `local_config()` turns advertising and browsing off and listens on a port the system picks, and `test_server_discovery.cpp` supplies its own `DiscoveryProvider` through `ServerConfig::discovery`.
- Tests: `cmake -B build-asan -DSENDSPIN_SERVER_ENABLE_WERROR=ON -DSENDSPIN_SERVER_BUILD_TESTS=ON -DSENDSPIN_SERVER_ENABLE_SANITIZERS=ON`, then `cmake --build build-asan` and `ctest --test-dir build-asan --output-on-failure`. The suites whose assertions depend on players keeping up with real time carry the CTest label `realtime` (`tests/CMakeLists.txt`); CI runs them on their own and the rest in parallel (`-LE realtime -j`). The tests also build `server_cli`'s WAV and FLAC readers, feeders, `--one-group`, timing figures and log and `--web` dashboard; `tests/temp_dir.h` gives a test a temporary directory, and `tests/job_view.h` a copy of what a group's pipelines read of a host view. `tests/test_ports.h` hands out loopback ports for test peers, and holds one that nothing listens on for a dial that must be refused; `tests/raw_client.h` is the raw test client (noise-c and Beast, either suite, scriptable misbehavior), `tests/silent_listener.h` a listener that holds the server's dials in their upgrade or handshake, `tests/test_recorder.h` a recording listener, an in-memory provider and `LocalServer`, a server started with them, `tests/wire_messages.h` the spec's binary message types and fields as the tests decode them, `tests/flac_reader.h` decodes a FLAC stream as a player gets it, `tests/opus_reader.h` an Opus stream, with the test tones and an SNR to compare lossy audio by, `tests/player_sink.h` holds the test signal and a real-time audio output for sendspin-cpp players, `tests/peer.h` what each in-process sendspin-cpp client shares (its network provider and blob store, its config and the URL the server dials, its main loop run on the test thread, the client_id at a URL), `tests/trust_fixtures.h` distinct PSKs and the client record a server saved, `tests/hex.h` hex text for known answers, `tests/gate.h` a gate a job waits at, the fixtures and harnesses a class's two test files share (`tests/host_view_fixture.h`, `tests/group_pipelines.h`, `tests/source_sink_rig.h`, `tests/session_harness.h`, `tests/trust_harness.h`), `tests/raw_roles.h` the messages a raw client sends to drive the roles and the readers of what it receives, `tests/players_rig.h` the rig, writes and checks of the in-process player tests, `tests/group_job.h` a group's pipeline job wrapped between runs, for the files compiled with `-fno-access-control` alone, and `tests/freed_blocks.h` counts the heap blocks freed still holding a secret, through the sanitizer's free hook (a test that needs it skips itself without ASan or TSan).
- Tests: `cmake -B build-asan -DSENDSPIN_SERVER_ENABLE_WERROR=ON -DSENDSPIN_SERVER_BUILD_TESTS=ON -DSENDSPIN_SERVER_ENABLE_SANITIZERS=ON`, then `cmake --build build-asan` and `ctest --test-dir build-asan --output-on-failure`. The suites whose assertions depend on players keeping up with real time carry the CTest label `realtime` (`tests/CMakeLists.txt`); CI runs them on their own and the rest in parallel (`-LE realtime -j`). The tests also build `server_cli`'s WAV and FLAC readers, feeders, `--one-group`, timing figures and log and `--web` dashboard; `tests/temp_dir.h` gives a test a temporary directory, and `tests/job_view.h` a copy of what a group's pipelines read of a host view. `tests/test_ports.h` hands out loopback ports for test peers, and holds one that nothing listens on for a dial that must be refused; `tests/raw_client.h` is the raw test client (noise-c and Beast, either suite, scriptable misbehavior), `tests/silent_listener.h` a listener that holds the server's dials in their upgrade or handshake, `tests/test_recorder.h` a recording listener, an in-memory provider and `LocalServer`, a server started with them, `tests/wire_messages.h` the spec's binary message types and fields as the tests decode them, `tests/flac_reader.h` decodes a FLAC stream as a player gets it, `tests/opus_reader.h` an Opus stream, with the test tones and an SNR to compare lossy audio by, `tests/player_sink.h` holds the test signal and a real-time audio output for sendspin-cpp players, `tests/peer.h` what each in-process sendspin-cpp client shares (its network provider and blob store, its config and the URL the server dials, its main loop run on the test thread, the client_id at a URL), `tests/trust_fixtures.h` distinct PSKs and the client record a server saved, `tests/hex.h` hex text for known answers, `tests/gate.h` a gate a job waits at, the fixtures and harnesses a class's two test files share (`tests/host_view_fixture.h`, `tests/group_pipelines.h`, `tests/source_sink_rig.h`, `tests/session_harness.h`, `tests/trust_harness.h`), `tests/raw_roles.h` the messages a raw client sends to drive the roles and the readers of what it receives, `tests/players_rig.h` the rig, writes and checks of the in-process player tests, `tests/group_job.h` a group's pipeline job wrapped between runs, for the files compiled with `-fno-access-control` alone, `tests/frame_payloads.h` frame payloads a test owns, in slots of their own, and `tests/freed_blocks.h` counts the heap blocks freed still holding a secret, and those a thread allocates, through the sanitizer's free and malloc hooks (a test that needs them skips itself without ASan or TSan).
- Soak: `tests/soak.cpp` (its trace checker in `tests/soak_wire_checker.h`, tested in the default suite), target `sendspin_server_cpp_soak` with its own entry point, registered as the CTest test `Soak` (label `soak`), which skips itself unless `SENDSPIN_SERVER_SOAK_SECONDS` is set (`SENDSPIN_SERVER_SOAK_SEED` picks the script); README.md has the command.
- CI also builds the library with Opus and discovery off and memory statistics on; as a subproject (`tests/consumer/`); and as the ESP-IDF component (`tests/esp_idf/`: `idf.py -C tests/esp_idf set-target esp32s3`, then `idf.py -C tests/esp_idf build`).
- Memory statistics: `SENDSPIN_SERVER_MEMORY_STATS` (default off; `CONFIG_SENDSPIN_SERVER_MEMORY_STATS` in the component's `Kconfig`) logs the heaps and the tasks' stacks at the server's start (on the thread calling `start()`) and, on the core thread, at a connection's start and end and a group's playback (`profile::MEMORY_STATS`); the tests count log lines and expect it off.
Expand Down
2 changes: 2 additions & 0 deletions cmake/sources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ set(SENDSPIN_SERVER_SOURCES
src/noise.cpp
# One pairing attempt: the decisions as pure functions, and its state and crypto
src/pairing.cpp
# A pipeline instance's frame payloads, in slots placed with the instance
src/payload_slots.cpp
src/pcm_convert.cpp
src/pipeline_stages.cpp
src/playback_policy.cpp
Expand Down
Loading
Loading