Skip to main content

Inner

Struct Inner 

Source
pub(crate) struct Inner {
    pub(crate) stop_state: AtomicI64,
    pub(crate) started_ok: AtomicBool,
    pub(crate) start_state: AtomicU8,
    pub(crate) last_error: Mutex<Option<String>>,
    pub(crate) capture_tid: AtomicI64,
    pub(crate) deliver_tid: AtomicI64,
    pub(crate) opus_bitrate: AtomicI32,
    pub(crate) use_silence_gate: AtomicBool,
    pub(crate) debug_logging: AtomicBool,
    pub(crate) emit_audio_header: AtomicBool,
}
Expand description

Shared state for one capture (or playback) run: the lifecycle state machine plus the per-frame settings mirrors the worker reads on the hot path — all lock-free except the rarely touched last_error.

The lifecycle is driven by two atomics — stop_state (the single source of truth for “should this run stop”) and start_state (IDLE → STARTING → RUNNING, then FAILED with a last_error or back to IDLE on a clean stop) — plus capture_tid and deliver_tid, the worker and delivery threads’ OS tids used to detect a re-entrant stop/start issued from inside the Python callback (which runs on the delivery thread). The remaining atomics mirror settings the worker consults each frame without re-snapshotting Settings, so update_audio_bitrate can retune the encoder mid-run without locking; the silence and header flags are published once at start and only read per frame.

Fields§

§stop_state: AtomicI64

Single lifecycle source of truth: STOP_NONE (running), STOP_EXTERNAL, or a positive tid meaning the run self-stopped from inside its own callback (recorded under the issuing thread’s tid — the delivery thread’s). A re-entrant start clears only its own self-stop via compare-exchange, so it can never clobber an external stop that raced in mid-join (which would strand it).

§started_ok: AtomicBool§start_state: AtomicU8§last_error: Mutex<Option<String>>

Why the last run failed (start_state == ST_FAILED), for Python’s last_error. Written before ST_FAILED is published and cleared when a new run is spawned, so a failure that lands after the start handshake returned — the retry ladder giving up, a mid-run reconnect budget spent, a worker panic — is still observable.

§capture_tid: AtomicI64

OS tid of the running capture thread; 0 when no worker is live.

§deliver_tid: AtomicI64

OS tid of the running delivery thread (the one that invokes the Python callback); 0 when none is live. Checked by the re-entrancy guards alongside capture_tid, because a stop/start issued from inside the callback executes on THIS thread — a join from it would cycle (stopper joins capture, capture joins delivery).

§opus_bitrate: AtomicI32

Lock-free per-frame settings mirrors, re-read by the worker each frame. opus_bitrate is republished by update_audio_bitrate; the rest are published once, by the run that starts.

§use_silence_gate: AtomicBool§debug_logging: AtomicBool§emit_audio_header: AtomicBool

Implementations§

Source§

impl Inner

Source

pub(crate) fn new() -> Self

Source

pub(crate) fn request_external_stop(&self)

Request an authoritative external stop, stored unconditionally.

The single source of truth for stopping a run. External stops win every race: they are published (inside the lifecycle lock, immediately before join) with a plain store, so a concurrent self-stop/self-start — which only ever compare-exchanges state it owns — can never clobber one and strand the join forever.

Source

pub(crate) fn request_self_stop(&self, me: i64)

Record a re-entrant self-stop from inside the run’s own callback.

Only transitions STOP_NONE -> me via compare-exchange, so it never overwrites a pending external stop (which must win the join).

Source

pub(crate) fn undo_self_stop(&self, me: i64)

Undo a re-entrant self-stop (self-start from inside the callback).

Compare-exchanges me -> STOP_NONE, clearing the stop only if this same thread still owns it. If an external stop landed in between, the CAS fails and that stop stands.

Source

pub(crate) fn clear_stop(&self)

Clear the stop state back to running. Only ever called under the lifecycle lock (after join, before spawn), where no external stop can be in flight — the lost-stop invariant that lets this be an unconditional store.

Source

pub(crate) fn stop_pending(&self) -> bool

True once any stop (external or self) is pending; the hot loops poll this.

Source

pub(crate) fn worker_alive(&self) -> bool

True while a worker is (or is still becoming) live: the startup handshake is in flight, or the hot loop is running with no stop pending.

Goes false the moment the worker fails, is stopped, or dies mid-run — the hot loop clears started_ok before breaking on error, even with no stop pending and start_state still RUNNING. Producers (e.g. AudioPlayback::write) gate on this so they surface a dead stream instead of feeding state nothing services.

Source

pub(crate) fn running(&self) -> bool

True while the worker is connected and running with no stop pending — the Python is_capturing / is_running getters.

Source

pub(crate) fn state_name(&self) -> &'static str

Lifecycle phase for Python’s state: "idle" (no run yet, or the last run stopped cleanly), "starting" (worker spawned, first PulseAudio session not yet up), "running" (connected, or reconnecting mid-run, with no stop pending), or "failed" (the last run ended in error; last_error says why). A run that was stopped reads "idle" again even while its thread is still winding down.

Source

pub(crate) fn fail(&self, msg: String)

Mark the run dead with a reason: logs msg to stderr, records it for last_error, and only then publishes ST_FAILED, so a reader that observes the failed state also finds its message. Every terminal worker error goes through here.

Source

pub(crate) fn last_error(&self) -> Option<String>

The message of the last run’s failure, or None while no run has failed since the last (re)start.

Source

pub(crate) fn clear_error(&self)

Forget the previous run’s failure; called by spawn_worker as a new run is armed.

Source

pub(crate) fn is_own_thread(&self, me: i64) -> bool

True when the calling thread is one of this run’s own threads — the capture worker or the delivery thread that runs the Python callback — i.e. the call is a re-entrant stop/start/drop from inside the callback. Such a caller must never join: teardown has the capture thread join the delivery thread, so a join from either one closes a cycle and deadlocks.

Auto Trait Implementations§

§

impl !Freeze for Inner

§

impl RefUnwindSafe for Inner

§

impl Send for Inner

§

impl Sync for Inner

§

impl Unpin for Inner

§

impl UnsafeUnpin for Inner

§

impl UnwindSafe for Inner

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> Ungil for T
where T: Send,