pub struct Scheduler {Show 17 fields
pub settings: Arc<Mutex<Settings>>,
pub pause_state: Arc<Mutex<PauseState>>,
pub camera_active: Arc<AtomicBool>,
pub video_active: Arc<AtomicBool>,
pub auto_suppress_reason: Arc<AtomicU8>,
pub config_path: PathBuf,
pub pause_path: PathBuf,
pub events_path: PathBuf,
pub screen_time_path: PathBuf,
pub timers: Arc<Mutex<BreakTimers>>,
pub stats: Arc<Mutex<BreakStats>>,
pub screen_time: Arc<Mutex<ScreenTimeState>>,
pub current_break: Arc<Mutex<Option<BreakEvent>>>,
pub logger: Logger,
pub profiles: Arc<Mutex<Vec<Profile>>>,
pub active_profile_name: Arc<Mutex<String>>,
pub hook_dialog_busy: Arc<AtomicBool>,
}Expand description
Live, mutable state for the break scheduler.
Constructed once in lib::run and shared across the app via
tauri::State and Arc-cloning. Every mutable field sits behind a
tokio::Mutex (or a std::sync::Mutex for the renderer-bound
current_break slot, which only needs short critical sections).
Clone is cheap — it bumps the inner Arcs.
The persisted paths (config_path, pause_path, etc.) are captured
at construction so the scheduler can write them back without
re-resolving Tauri’s app_data_dir each tick.
§Locking convention: no nested async mutexes across .await
Every call site in this module releases a tokio::Mutex guard
before acquiring the next one across an .await point. The pattern
is “snapshot then act”:
let s = sched.settings.lock().await.clone(); // release before next lock
let name = sched.active_profile_name.lock().await.clone();
let mut profiles = sched.profiles.lock().await; // safe — others releasedFollowing this rule, deadlock becomes structurally impossible — the
classic “thread A holds X waiting for Y, thread B holds Y waiting
for X” cycle cannot form if guards never overlap on .await.
What this rules out:
let s = sched.settings.lock().await; let p = sched.profiles.lock().await;(holdingsettingsacross theprofilesacquisition)let g = sched.timers.lock().await; some_async_fn(&sched).await;(holding any guard across a call that may itself lock the same scheduler)
What it allows:
- Re-acquiring the same lock back-to-back to mutate after an awaited side-effect (write to disk, emit event). Each scope drops first.
- The std
current_breakmutex, which is only ever taken inside short non-async blocks (seeoverlay::fire_break). - Short synchronous emits (
app.emit("evt", &single_field)) that borrow a guard expression in the argument list and drop it at the end of the statement — the emit itself does not.awaitand yields no scheduler lock. - Reading two unrelated single-field snapshots back-to-back inside
one command (see
get_postpone_state): clone the first, drop, then acquire the second. Brief observational skew is fine for renderer queries that never make causal decisions across the pair.
If a new code path genuinely needs nested holds — say, an atomic read-modify-write across two pieces of state — consolidate them into one struct under one mutex instead of introducing the nesting.
Fields§
§settings: Arc<Mutex<Settings>>§pause_state: Arc<Mutex<PauseState>>§camera_active: Arc<AtomicBool>§video_active: Arc<AtomicBool>§auto_suppress_reason: Arc<AtomicU8>0 = not auto-suppressed; otherwise SuppressReason::from_u8
decodes which guard fired. The tray reads this each tick to
pick between the Inactive icon + reason tooltip vs the Normal
icon. Atomic instead of a mutex so the per-tick read is free.
config_path: PathBuf§pause_path: PathBuf§events_path: PathBuf§screen_time_path: PathBuf§timers: Arc<Mutex<BreakTimers>>§stats: Arc<Mutex<BreakStats>>§screen_time: Arc<Mutex<ScreenTimeState>>§current_break: Arc<Mutex<Option<BreakEvent>>>§logger: Logger§profiles: Arc<Mutex<Vec<Profile>>>§active_profile_name: Arc<Mutex<String>>§hook_dialog_busy: Arc<AtomicBool>Implementations§
Source§impl Scheduler
impl Scheduler
Sourcepub async fn tray_countdown_snapshot(&self) -> (TrayCountdownSnapshot, bool)
pub async fn tray_countdown_snapshot(&self) -> (TrayCountdownSnapshot, bool)
Snapshot the per-tick state the tray ticker needs. Polled once
per second on macOS/Linux. See TrayCountdownSnapshot for the
precedence rules.
Returns (snapshot, text_enabled). text_enabled mirrors the
user’s tray_countdown_enabled setting — the ticker uses it to
gate the always-visible title text (icon + tooltip aren’t
gated, since the icon is the visual signal and the tooltip is
hover-only opt-in).
Source§impl Scheduler
impl Scheduler
Sourcepub fn new(
config_path: PathBuf,
pause_path: PathBuf,
events_path: PathBuf,
screen_time_path: PathBuf,
) -> Self
pub fn new( config_path: PathBuf, pause_path: PathBuf, events_path: PathBuf, screen_time_path: PathBuf, ) -> Self
Load persisted state from disk and spawn the camera / video
monitor threads. Does not start the main scheduler loop —
call spawn for that, after app.manage-ing the result.
Sourcepub fn spawn(&self, app: AppHandle)
pub fn spawn(&self, app: AppHandle)
Launch the 1Hz scheduler loop on the Tauri async runtime. Safe
to call exactly once per Scheduler instance.
Sourcepub async fn snapshot_profiles_file(&self) -> ProfilesFile
pub async fn snapshot_profiles_file(&self) -> ProfilesFile
Build the on-disk shape ({ profiles, active }) by snapshotting
the in-memory profile list. Used by persist_profiles.