Centralized parameter storage with lock-free value access. More...
#include <store.hpp>
Classes | |
| struct | ParamWrite |
One parameter write in a set_values_rt() batch. More... | |
| struct | ModulationWatchers |
| class | ModulationWatch |
| RAII handle for watch_modulation(). Move-only; dropping it unwatches. More... | |
Public Member Functions | |
| StateStore () | |
| ~StateStore () | |
| StateStore (const StateStore &)=delete | |
| StateStore & | operator= (const StateStore &)=delete |
| void | add_parameter (const ParamInfo &info) |
| Register a parameter. | |
| void | add_group (const ParamGroup &group) |
| Register a parameter group. Call during initialization only. | |
| float | get_value (ParamID id) const |
| Read a parameter's base value (lock-free). | |
| ParamReader | param_reader (ParamID id) const |
| Resolve a read handle for a parameter's base value. | |
| float | get_modulated (ParamID id) const |
| Read a parameter's modulated value: base + mod_offset (lock-free). | |
| BaseOffsetValue | get_base_offset (ParamID id) const |
| Read the canonical base-vs-offset pair without collapsing its fields. | |
| void | set_value (ParamID id, float value) |
| Write a parameter's base value (lock-free). | |
| void | set_value_rt (ParamID id, float value) |
| Real-time-safe parameter write for use from the audio thread. | |
| void | set_values_rt (std::span< const ParamWrite > writes) |
set_value_rt() for several parameters as one store write: one writer claim for the batch and one listener-snapshot lease for its notifications. | |
| void | set_normalized_rt (ParamID id, float normalized) |
| Real-time-safe normalized-value write. | |
| std::optional< ParameterFeedbackBaseline > | capture_feedback_baseline (ParamID id) const noexcept |
| Capture an exact per-parameter identity at the caller's ordering point. | |
| ConditionalParameterApplyResult | set_value_rt_if_baseline (const ParameterFeedbackBaseline &baseline, float value) |
| Publish asynchronous feedback only if the captured identity is still current. | |
| void | set_mod_offset (ParamID id, float offset) |
| Set the absolute modulation offset for a parameter. | |
| void | add_mod_offset (ParamID id, float delta) |
| Add a delta to a parameter's modulation offset. | |
| void | reset_all_mod () |
| Clear all modulation offsets to zero. | |
| void | set_display_modulation (ParamID id, float plain_value) noexcept |
Publish the value (plain units) a plugin's own modulator plays for id. | |
| void | clear_display_modulation (ParamID id) noexcept |
Stop showing a plugin-published modulated value for id. | |
| std::optional< float > | displayed_modulation (ParamID id) const noexcept |
The modulated value a control should show, normalized to the parameter's range, or nullopt when nothing modulates id. | |
| ModulationWatch | watch_modulation (ParamID id, std::function< void(std::optional< float >)> on_change) |
Call on_change on the main thread, from pump_listeners(), whenever displayed_modulation(id) changes (nullopt when it stops). | |
| float | get_normalized (ParamID id) const |
| Read a parameter's value mapped to [0, 1] (lock-free). | |
| void | set_normalized (ParamID id, float normalized) |
| Write a parameter from a normalized [0, 1] value (lock-free). | |
| std::uint64_t | state_generation () const noexcept |
| Current mutation generation for every parameter-state writer. | |
| bool | state_snapshot_is_current (std::uint64_t generation) const noexcept |
True when no writer is in flight and none has advanced the generation past generation. | |
| ParameterGestureApplyResult | apply_normalized_gesture_if_generation (std::uint64_t expected_generation, ParamID id, float normalized) |
Atomically claim expected_generation and apply one complete host gesture if it is still current. | |
| float | get_default (ParamID id) const |
| Get the default value for a parameter (from its ParamRange). | |
| void | reset_to_default (ParamID id) |
| Reset a single parameter to its default value. | |
| void | reset_all_to_defaults () |
| Reset all parameters to their default values. | |
| bool | reset_triggers_rt () |
| Reset every trigger / momentary parameter to its range default. | |
| bool | has_trigger_params () const |
| True when at least one registered parameter is a trigger / momentary control. | |
| const ParamInfo * | info (ParamID id) const |
| Look up immutable metadata for a parameter. | |
| bool | set_parameter_display_name (ParamID id, std::string name) |
| Override only the host-facing presentation name of a stable parameter. | |
| std::string | parameter_display_name (ParamID id) const |
| Current host-facing name: overlay when present, otherwise ParamInfo::name. | |
| std::uint64_t | parameter_display_revision () const noexcept |
| Monotonic edge for format adapters that republish ParameterInfo. | |
| template<std::size_t N> | |
| std::array< float, N > | snapshot (const std::array< ParamID, N > &ids) const noexcept |
Block-local snapshot of N parameter values. | |
| template<std::size_t N> | |
| std::array< float, N > | snapshot_modulated (const std::array< ParamID, N > &ids) const noexcept |
Modulated variant of snapshot — returns each parameter's base + per-buffer modulation offset (see set_mod_offset). | |
| std::span< const ParamInfo > | all_params () const |
| View of all registered parameters (in registration order). | |
| std::span< const ParamGroup > | all_groups () const |
| View of all registered groups (in registration order). | |
| std::size_t | param_count () const |
| Number of registered parameters. | |
| void | begin_gesture (ParamID id) |
| Signal the host that a gesture (drag, click) has begun on a parameter. | |
| void | end_gesture (ParamID id) |
| Signal the host that the current gesture has ended. | |
| void | acquire_gesture (ParamID id) |
| Acquire/release a shared gesture lease. | |
| void | release_gesture (ParamID id) |
| void | defer_gesture_release (ParamID id) |
| Release a lease without synchronously entering host code. | |
| void | flush_deferred_gesture_releases () noexcept |
| void | release_open_gestures () |
| Close every gesture still open and report each end to the host. | |
| std::size_t | open_gesture_count () const noexcept |
| Number of parameters with an open gesture (begin without a matching end). | |
| void | run_gesture_on_main (std::function< void()> gesture) |
| Run a complete parameter gesture (begin_gesture → value writes → end_gesture) on the host's main thread, from ANY thread. | |
| std::uint64_t | gesture_thread_violation_count () const |
Number of times a gesture entry point (begin_gesture / end_gesture) was called off the host main thread while a main-thread backend was registered. | |
| void | set_main_loop (pulp::events::EventLoop *loop) |
Install an EventLoop used to marshal ListenerThread::Main listeners onto the main thread. | |
| ListenerToken | add_listener (ParamChangeCallback callback, ListenerThread thread) |
| Subscribe to parameter-value changes. | |
| ListenerToken | add_listener (ParamChangeCallback callback, ListenerThread thread, ListenerRestoreBehavior restore_behavior) |
| Subscribe with explicit state-restore behavior. | |
| ListenerToken | add_audio_listener (ParamChangeCallback callback) |
| Convenience for opting into real-time-safe inline invocation. | |
| void | remove_listener (ListenerToken &token) |
| Remove a listener explicitly. | |
| std::size_t | pump_listeners () |
Drain queued main-thread listener invocations posted by set_value_rt() / set_normalized_rt() from the audio thread. | |
| std::size_t | reconcile_restore_listeners () |
| Reconcile opted-in Main listeners with the current parameter snapshot. | |
| RtListenerQueueTelemetry | rt_listener_queue_telemetry () const |
Latest telemetry for the RT-to-main listener queue used by set_value_rt() / pump_listeners(). | |
| void | reset_rt_listener_queue_overflow_count () |
| Reset only the RT listener queue overflow counter. | |
| void | add_listener (ParamChangeCallback callback) |
| Legacy permanent-listener registration. | |
| std::vector< uint8_t > | serialize () const |
| Serialize all parameter values to a binary blob for preset/state save. | |
| bool | deserialize (std::span< const uint8_t > data) |
| Restore parameter values from a parameter-only binary blob. | |
| std::uint64_t | state_restore_revision () const noexcept |
| Monotonic reconciliation edge published after each successful deserialize(). | |
| void | set_state_version (uint32_t version) |
| Set the schema version embedded in serialized state. | |
| uint32_t | state_version () const |
| Get the current state schema version. | |
| bool | register_state_migration (uint32_t from_version, uint32_t to_version, StateMigrationRegistry::MigrationFn migration) |
| Register a load-time migration for this store's parameter schema. | |
| void | copy_state_migrations_from (const StateStore &source) |
| Copy schema migrations from another store with the same registered parameters. | |
| void | set_gesture_callbacks (std::function< void(ParamID)> begin_fn, std::function< void(ParamID)> end_fn) |
| Install callbacks that forward gesture begin/end to the plugin host. | |
Friends | |
| struct | test_support::StateWriterProbe |
| class | ParamReader |
Centralized parameter storage with lock-free value access.
StateStore is the single source of truth for all plugin parameters. Register parameters at initialization via add_parameter(), then read/write values from any thread using the lock-free accessors.
| pulp::state::StateStore::StateStore | ( | ) |
| pulp::state::StateStore::~StateStore | ( | ) |
|
delete |
| void pulp::state::StateStore::acquire_gesture | ( | ParamID | id | ) |
Acquire/release a shared gesture lease.
Multiple live editors may bind controls to the same StateStore parameter; only the first lease emits begin_gesture() and only the last release emits end_gesture(). Main-thread only — see begin_gesture().
|
nodiscard |
Convenience for opting into real-time-safe inline invocation.
Equivalent to add_listener(cb, ListenerThread::Audio).
| void pulp::state::StateStore::add_group | ( | const ParamGroup & | group | ) |
Register a parameter group. Call during initialization only.
| void pulp::state::StateStore::add_listener | ( | ParamChangeCallback | callback | ) |
Legacy permanent-listener registration.
Prefer the ListenerToken-returning overload above for new code.
The callback is invoked inline on whichever thread fires set_value() and cannot be removed for the lifetime of the StateStore. Pulp's own subsystems are migrating to the token API; this overload remains for one release as a compatibility shim.
|
nodiscard |
Subscribe to parameter-value changes.
The returned ListenerToken owns the subscription. Destroy it (or call ListenerToken::reset()) to remove the listener. The token MUST outlive the listener's expected firing window — discarding it removes the subscription immediately.
| callback | Invoked for every change. Pass an empty ParamChangeCallback to register a no-op slot. |
| thread | ListenerThread::Main (default) marshals the callback through the installed EventLoop; ListenerThread::Audio runs it inline on the firing thread and asserts caller RT-safety. |
| restore_behavior | ListenerRestoreBehavior::Silent preserves deserialize's listener-silent contract. UI bindings whose cached presentation must follow restored state opt into ListenerRestoreBehavior::Reconcile. |
set_value_rt / set_normalized_rt, drained by pump_listeners) are CURRENT-VALUE notifications: the float passed to the callback is a live snapshot read at drain time (not the value queued when the change fired), and deliveries are COALESCED to one call per changed parameter per pump. This keeps the value coherent under dense automation — a burst of N writes to one parameter yields a single callback carrying the latest value, never a sequence of stale intermediate values. ListenerThread::Audio listeners are unaffected: they fire inline per change with that change's value.
|
nodiscard |
Subscribe with explicit state-restore behavior.
| void pulp::state::StateStore::add_mod_offset | ( | ParamID | id, |
| float | delta ) |
Add a delta to a parameter's modulation offset.
| void pulp::state::StateStore::add_parameter | ( | const ParamInfo & | info | ) |
Register a parameter.
Call during initialization only.
| info | Immutable metadata for this parameter. |
|
inline |
View of all registered groups (in registration order).
|
inline |
View of all registered parameters (in registration order).
| ParameterGestureApplyResult pulp::state::StateStore::apply_normalized_gesture_if_generation | ( | std::uint64_t | expected_generation, |
| ParamID | id, | ||
| float | normalized ) |
Atomically claim expected_generation and apply one complete host gesture if it is still current.
A stale caller fails before begin_gesture. Callback failure restores the old value when no newer writer has replaced it. A concurrent writer is never overwritten during rollback; the caller must refresh when the result is not Applied.
| void pulp::state::StateStore::begin_gesture | ( | ParamID | id | ) |
Signal the host that a gesture (drag, click) has begun on a parameter.
Main-thread only. The registered gesture callbacks forward to the host's undo-grouping calls — VST3 beginEdit, AU AudioUnitEvent(kAudioUnitEvent_BeginParameterChangeGesture), CLAP CLAP_EVENT_PARAM_GESTURE_BEGIN — all of which are only legal on the host UI/main thread. Call this from the UI thread before a series of set_value() calls so the host groups them into a single undo step.
A background writer (e.g. a MIDI-learn engine setting a mapped parameter from a timer thread) must NOT call this directly; route the whole gesture through run_gesture_on_main(). When a main-thread backend is registered and this is called off it, the misuse is counted (see gesture_thread_violation_count) and, in debug builds, asserts.
|
noexcept |
Capture an exact per-parameter identity at the caller's ordering point.
This is a single lock-free load and performs no allocation.
|
noexcept |
Stop showing a plugin-published modulated value for id.
| void pulp::state::StateStore::copy_state_migrations_from | ( | const StateStore & | source | ) |
Copy schema migrations from another store with the same registered parameters.
Used by restore probes that validate before touching live state.
| void pulp::state::StateStore::defer_gesture_release | ( | ParamID | id | ) |
Release a lease without synchronously entering host code.
The matching host end callback is queued for flush_deferred_gesture_releases().
| bool pulp::state::StateStore::deserialize | ( | std::span< const uint8_t > | data | ) |
Restore parameter values from a parameter-only binary blob.
Format adapters may decode a higher-level host blob first, then pass only the embedded StateStore payload here.
|
noexcept |
The modulated value a control should show, normalized to the parameter's range, or nullopt when nothing modulates id.
| void pulp::state::StateStore::end_gesture | ( | ParamID | id | ) |
Signal the host that the current gesture has ended.
Main-thread only — see begin_gesture().
|
noexcept |
|
inline |
Number of times a gesture entry point (begin_gesture / end_gesture) was called off the host main thread while a main-thread backend was registered.
Always zero in headless tests (no backend) and for correctly main-thread-confined UIs. A non-zero value flags a threading bug — a background writer that bypassed run_gesture_on_main.
| BaseOffsetValue pulp::state::StateStore::get_base_offset | ( | ParamID | id | ) | const |
Read the canonical base-vs-offset pair without collapsing its fields.
| float pulp::state::StateStore::get_default | ( | ParamID | id | ) | const |
Get the default value for a parameter (from its ParamRange).
| float pulp::state::StateStore::get_modulated | ( | ParamID | id | ) | const |
Read a parameter's modulated value: base + mod_offset (lock-free).
| float pulp::state::StateStore::get_normalized | ( | ParamID | id | ) | const |
Read a parameter's value mapped to [0, 1] (lock-free).
|
inline |
Read a parameter's base value (lock-free).
|
inline |
True when at least one registered parameter is a trigger / momentary control.
Adapters can skip calling reset_triggers_rt() when false.
Look up immutable metadata for a parameter.
id is not registered.
|
inlinenoexcept |
Number of parameters with an open gesture (begin without a matching end).
Observability for tests and adapters.
|
delete |
|
inline |
Number of registered parameters.
| ParamReader pulp::state::StateStore::param_reader | ( | ParamID | id | ) | const |
Resolve a read handle for a parameter's base value.
Call off the audio thread (e.g. in prepare()); the returned handle's get() is cheaper than get_value() because the ID lookup is already done. An unknown ID yields an unbound handle that reads 0.0f.
| std::string pulp::state::StateStore::parameter_display_name | ( | ParamID | id | ) | const |
Current host-facing name: overlay when present, otherwise ParamInfo::name.
Returns an owned string so a concurrent overlay publication cannot invalidate the caller's view.
|
inlinenoexcept |
Monotonic edge for format adapters that republish ParameterInfo.
| std::size_t pulp::state::StateStore::pump_listeners | ( | ) |
Drain queued main-thread listener invocations posted by set_value_rt() / set_normalized_rt() from the audio thread.
Call this from the main thread (e.g. the editor's per-frame timer or the host's UI tick).
| std::size_t pulp::state::StateStore::reconcile_restore_listeners | ( | ) |
Reconcile opted-in Main listeners with the current parameter snapshot.
State restore writes parameter atomics without invoking listeners because the host may deserialize on a non-UI thread. ViewBridge calls this from its main-thread idle tick after observing a successful deserialize edge. Only listeners registered with ListenerRestoreBehavior::Reconcile participate; ordinary Main and Audio listeners are never invoked.
The restore revision is consumed store-wide, so multiple ViewBridges sharing this store do not replay the same callbacks. Each bridge still tracks the edge independently so every attached view repaints.
Listener additions and removals during callbacks are safe. A listener removed before its turn is skipped, and a listener added during this pass participates only in the next reconciliation.
| bool pulp::state::StateStore::register_state_migration | ( | uint32_t | from_version, |
| uint32_t | to_version, | ||
| StateMigrationRegistry::MigrationFn | migration ) |
Register a load-time migration for this store's parameter schema.
Migrations are scoped to the StateStore instance because state schema versions are plugin-defined; two plugins may both need a different migration from version 1 to 2.
| void pulp::state::StateStore::release_gesture | ( | ParamID | id | ) |
| void pulp::state::StateStore::release_open_gestures | ( | ) |
Close every gesture still open and report each end to the host.
The store tracks which parameters have an open gesture (a begin_gesture with no matching end_gesture) so that begin/end are balanced 1:1 toward the host — a second begin_gesture on an already-open parameter is suppressed, and an end_gesture on a parameter that was never opened is a no-op. An editor torn down mid-drag (the user closes the plugin window without lifting the mouse) would otherwise leave the host's automation record open forever; a format adapter calls this on editor teardown to release everything still held. Main-thread only — see begin_gesture().
| void pulp::state::StateStore::remove_listener | ( | ListenerToken & | token | ) |
Remove a listener explicitly.
Equivalent to letting the token fall out of scope.
| void pulp::state::StateStore::reset_all_mod | ( | ) |
Clear all modulation offsets to zero.
| void pulp::state::StateStore::reset_all_to_defaults | ( | ) |
Reset all parameters to their default values.
| void pulp::state::StateStore::reset_rt_listener_queue_overflow_count | ( | ) |
Reset only the RT listener queue overflow counter.
Queued events are untouched.
| void pulp::state::StateStore::reset_to_default | ( | ParamID | id | ) |
Reset a single parameter to its default value.
| bool pulp::state::StateStore::reset_triggers_rt | ( | ) |
Reset every trigger / momentary parameter to its range default.
A trigger parameter (see ParamInfo::is_trigger / a Reset designation) is a one-shot "do this now" control. Format adapters call this once at the end of each process block, after the Processor has observed the value, so the control returns to its resting default for the next block.
Real-time safe: writes directly to the lock-free atomic value of each trigger parameter (no allocation, no listener dispatch, no queueing). The set of trigger indices is cached at registration, so this is O(number of trigger params), and a store with no triggers does no work.
| RtListenerQueueTelemetry pulp::state::StateStore::rt_listener_queue_telemetry | ( | ) | const |
Latest telemetry for the RT-to-main listener queue used by set_value_rt() / pump_listeners().
| void pulp::state::StateStore::run_gesture_on_main | ( | std::function< void()> | gesture | ) |
Run a complete parameter gesture (begin_gesture → value writes → end_gesture) on the host's main thread, from ANY thread.
This is the enforced marshalling path for background writers. If a main-thread backend is registered (i.e. inside a real host) and the caller is off it, gesture is posted via events::MainThreadDispatcher::call_async and runs later on the main thread. Otherwise (already on the main thread, or a headless test with no backend) it runs inline. The whole begin→set→end sequence must be posted as one unit so the host sees the calls in order on one thread — posting only begin/end while writing the value elsewhere would corrupt undo grouping.
| std::vector< uint8_t > pulp::state::StateStore::serialize | ( | ) | const |
Serialize all parameter values to a binary blob for preset/state save.
This is the parameter-only payload. Format adapters may wrap it in a higher-level host blob alongside Processor::serialize_plugin_state().
|
noexcept |
Publish the value (plain units) a plugin's own modulator plays for id.
Display only. Real-time safe (one relaxed store).
|
inline |
Install callbacks that forward gesture begin/end to the plugin host.
Format adapters (VST3, AU, CLAP) call this during initialization so that UI-driven gestures are reported to the host for undo grouping.
| void pulp::state::StateStore::set_main_loop | ( | pulp::events::EventLoop * | loop | ) |
Install an EventLoop used to marshal ListenerThread::Main listeners onto the main thread.
If unset, Main listeners run inline on whichever thread fired set_value().
Format adapters and standalone hosts install this during plugin initialization; tests usually leave it unset.
| void pulp::state::StateStore::set_mod_offset | ( | ParamID | id, |
| float | offset ) |
Set the absolute modulation offset for a parameter.
| void pulp::state::StateStore::set_normalized | ( | ParamID | id, |
| float | normalized ) |
Write a parameter from a normalized [0, 1] value (lock-free).
| void pulp::state::StateStore::set_normalized_rt | ( | ParamID | id, |
| float | normalized ) |
Real-time-safe normalized-value write.
Denormalizes through the parameter's ParamRange and then dispatches through set_value_rt(). Used by the VST3 adapter where the host reports parameter changes in normalized [0, 1] form.
| bool pulp::state::StateStore::set_parameter_display_name | ( | ParamID | id, |
| std::string | name ) |
Override only the host-facing presentation name of a stable parameter.
The registered ParamInfo remains immutable: its ID, ordering, range, value, and authoring name do not change. Format adapters may use this overlay when a plugin keeps a fixed automation slot but changes the semantic label shown by the host (for example, a generated macro slot changing from "Tone" to "Cutoff").
Passing an empty name clears the overlay and restores ParamInfo::name. Safe to call from non-audio threads while processing is active. Readers observe an immutable copy-on-write snapshot.
|
inline |
Set the schema version embedded in serialized state.
Increment this when the parameter layout changes between plugin versions.
| void pulp::state::StateStore::set_value | ( | ParamID | id, |
| float | value ) |
Write a parameter's base value (lock-free).
| void pulp::state::StateStore::set_value_rt | ( | ParamID | id, |
| float | value ) |
Real-time-safe parameter write for use from the audio thread.
Performs the atomic value store (lock-free, no allocation) and fires registered ListenerThread::Audio listeners inline. Any ListenerThread::Main listeners are NOT invoked here — the change is pushed onto a single-producer/single-reader queue and drained by pump_listeners() on the main thread. This avoids the EventLoop::dispatch lambda allocation that the generic set_value() path performs when a Main listener exists.
Use this from format-adapter audio callbacks (CLAP / VST3 / AU process) for host-driven parameter writes. UI-driven writes from the main thread should use set_value().
pump_listeners() is not called fast enough the oldest queued events are dropped (the atomic value is still up-to-date, so the UI picks up the latest on its next pump). Use rt_listener_queue_telemetry() to inspect this pressure. | ConditionalParameterApplyResult pulp::state::StateStore::set_value_rt_if_baseline | ( | const ParameterFeedbackBaseline & | baseline, |
| float | value ) |
Publish asynchronous feedback only if the captured identity is still current.
The packed identity CAS is the linearization point. A conflict leaves the newer value untouched and emits no notification. current_identity is diagnostic only; callers recapture a fresh baseline before retrying and must not treat it as an authority token. Revision exhaustion is reported as Conflict and never wraps.
| void pulp::state::StateStore::set_values_rt | ( | std::span< const ParamWrite > | writes | ) |
set_value_rt() for several parameters as one store write: one writer claim for the batch and one listener-snapshot lease for its notifications.
Writes apply in batch order, each with its own state generation, exactly as consecutive set_value_rt() calls would: a repeated ID ends at its last value, and a write an Audio listener makes during the batch is overwritten by a later batch write to the same parameter. Every applied write notifies its parameter's listeners once, and Audio listeners run inside the batch. Real-time safe; format adapters use it to write a block's automation.
|
nodiscardnoexcept |
Block-local snapshot of N parameter values.
Loads each parameter once and returns the values in a stack- allocated std::array. DSP code should call this once at the top of process() and read from the returned array inside the per-sample loop, instead of calling get_value() per sample (each per-sample atomic load is a memory fence and fans out across cores). Mirrors the usual audio-thread pattern: snapshot the values you need before entering the inner loop.
| N | Compile-time count of parameter IDs to read. |
| ids | Parameter IDs to snapshot, in the desired output order. Unknown IDs yield 0.0f at their slot. |
ids.
|
nodiscardnoexcept |
Modulated variant of snapshot — returns each parameter's base + per-buffer modulation offset (see set_mod_offset).
Use this from a synth that consumes CLAP's modulated values inside the voice loop.
|
inlinenoexcept |
Current mutation generation for every parameter-state writer.
Base-value writes (UI and audio), modulation changes, trigger resets, state restore, and generation-bound gestures all advance this one monotonic source. Registration does not: parameters are defined before the live store is published.
|
inlinenoexcept |
Monotonic reconciliation edge published after each successful deserialize().
Deserialization deliberately does not dispatch parameter listeners: format hosts may restore from a thread where author/UI callbacks are unsafe. An attached ViewBridge polls this revision on its main-thread idle tick and schedules one editor repaint, whose paint boundary can then reconcile listener-silent control state.
This is an edge, not a count of host-level restore successes. A higher-level restore that applies parameter state and then rejects its plugin-owned payload may deserialize the previous parameter snapshot as rollback, advancing this revision twice. Consumers compare for inequality and may coalesce either advance into one refresh of the final state.
|
inlinenoexcept |
True when no writer is in flight and none has advanced the generation past generation.
A writer advances the generation BEFORE it stores its value, so a snapshot of values is trustworthy only when this holds both before the values are read and after (see format::boundary::refresh_param_snapshot); checking only afterwards accepts a snapshot that read a value a writer was about to replace.
|
inline |
Get the current state schema version.
|
nodiscard |
Call on_change on the main thread, from pump_listeners(), whenever displayed_modulation(id) changes (nullopt when it stops).
The watch lasts as long as the returned token. Main thread only.
|
friend |
|
friend |