//! Coalesced dispatcher for short-lived system side effects driven by
//! UI events.
//!
//! Used to back system setters (`brightness::set_brightness`,
//! `audio::set_volume`, `audio::set_muted`) that originally ran their
//! side effect synchronously on every slider `on_change`. At drag
//! rates (60+ Hz) that floods the kernel with `fork` + `exec` calls
//! and orphan zombies — on the Librem 5's Cortex-A53, enough to make
//! the slider crawl. Routing through a worker thread gives:
//!
//! * **Throttling for free.** The worker runs the side effect and
//!   waits for it before pulling the next value. While it waits,
//!   additional sends pile up in the channel; on the next iteration
//!   the worker drains the queue and uses only the latest value,
//!   dropping the intermediates. The effective rate self-tunes to "as
//!   fast as the side effect can finish", which is exactly the cadence
//!   the underlying subsystem (PipeWire / sysfs / DBus) can act on.
//! * **No zombies.** Workers that spawn subprocesses `wait()` on the
//!   `Child` themselves, so the process table stays clean even after
//!   thousands of drag events. Workers that go straight to sysfs (e.g.
//!   `brightness::set_brightness`'s direct write path) do not spawn
//!   anything in the first place.
//! * **Cheap call site.** `set_brightness` / `set_volume` become a
//!   non-blocking channel send (~hundreds of ns), so the UI thread no
//!   longer pays `fork` + `exec` (or `write` + `fsync`) on every event.

use std::sync::mpsc::{ self, Sender };

/// Spin up a worker thread bound to `work` and return the channel the
/// UI thread should push values into. The worker loops:
///
/// 1. `recv()` the next value (blocks indefinitely while idle).
/// 2. Drain any further values that arrived while step 3 was running
///    and keep only the latest — the coalescing step.
/// 3. Call `work( v )` synchronously. The closure is responsible for
///    its own error logging and, if it spawns a subprocess, for
///    `wait()`-ing on the resulting `Child` so the process table does
///    not leak.
pub fn spawn_worker<T: Send + 'static>(
	work: impl Fn( T ) + Send + 'static,
) -> Sender<T>
{
	let ( tx, rx ) = mpsc::channel::<T>();
	std::thread::spawn( move ||
	{
		while let Ok( mut v ) = rx.recv()
		{
			while let Ok( newer ) = rx.try_recv() { v = newer; }
			work( v );
		}
	} );
	tx
}
