Skip to main content

Scheduler

Struct Scheduler 

Source
pub struct Scheduler {
Show 25 fields pub settings: Arc<Mutex<Settings>>, pub pause_state: Arc<Mutex<PauseState>>, pub camera_active: Arc<AtomicBool>, pub video_active: Arc<AtomicBool>, pub plugin_suppress: 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 chores_path: PathBuf, pub plugins_path: PathBuf, pub plugins: Arc<Mutex<PluginRegistry>>, pub plugin_dialog_busy: Arc<AtomicBool>, pub timers: Arc<Mutex<BreakTimers>>, pub stats: Arc<Mutex<BreakStats>>, pub screen_time: Arc<Mutex<ScreenTimeState>>, pub chores: Arc<Mutex<ChoresState>>, 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>, pub onboarding_completed: Arc<AtomicBool>, pub import_in_progress: 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 released

Following 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; (holding settings across the profiles acquisition)
  • 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_break mutex, which is only ever taken inside short non-async blocks (see overlay::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 .await and 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>§plugin_suppress: Arc<AtomicBool>

Whether any installed detector plugin currently votes to suppress breaks. Written by the off-tick detector-eval task, read by the 1Hz loop’s suppression chain — like camera_active, an atomic so the per-tick read is free.

§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§chores_path: PathBuf

The day’s chore “post-it” (chores.json beside screen_time.json): a daily-reset list the user enters and the overlay surfaces as a long-break nudge. Global, not per-profile — a chore list is about the day, not the active settings profile.

§plugins_path: PathBuf

Installed content plugins + the merge-and-track record of what each added, persisted to plugins.json beside settings.json. See docs/developer/plugin-api-design.md.

§plugins: Arc<Mutex<PluginRegistry>>§plugin_dialog_busy: Arc<AtomicBool>

Single-flight guard for the plugin-install confirmation dialog, mirroring Scheduler::hook_dialog_busy.

§timers: Arc<Mutex<BreakTimers>>§stats: Arc<Mutex<BreakStats>>§screen_time: Arc<Mutex<ScreenTimeState>>§chores: Arc<Mutex<ChoresState>>§current_break: Arc<Mutex<Option<BreakEvent>>>§logger: Logger§profiles: Arc<Mutex<Vec<Profile>>>§active_profile_name: Arc<Mutex<String>>§hook_dialog_busy: Arc<AtomicBool>§onboarding_completed: Arc<AtomicBool>

Whether first-run onboarding has been completed. Mirrors ProfilesFile::onboarding_completed; persisted back to disk via snapshot_profiles_file. Atomic so the IPC commands can read and flip it without taking the profiles lock.

§import_in_progress: Arc<AtomicBool>

Set by the backup-import flow while it’s mid-restore. The run loop short-circuits each tick while this is true so it can’t fire a break with mid-write state (e.g. new events.jsonl on disk but old settings still in memory).

Implementations§

Source§

impl Scheduler

Source

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

Source

pub fn new( config_path: PathBuf, pause_path: PathBuf, events_path: PathBuf, screen_time_path: PathBuf, chores_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.

Source

pub(crate) async fn resolve_chore_prompt( &self, kind: BreakKind, ) -> Option<String>

Resolve the day’s chore nudge for a firing break. Long breaks only — micro is too short and bedtime is for winding down. Rolls the list over at local midnight, advances the rotation cursor so consecutive long breaks suggest different tasks, and persists whenever either changed. Shared by the scheduled-fire and manual-trigger paths so the rollover/rotation logic lives in exactly one place.

Source

pub fn spawn(&self, app: AppHandle)

Launch the 1Hz scheduler loop on the Tauri async runtime. Safe to call exactly once per Scheduler instance.

Source

fn spawn_detector_eval(&self)

Run the installed detector plugins off the 1Hz tick, on a throttled interval, and publish their aggregate verdict to plugin_suppress for the run loop to read. The wasm work happens on a blocking thread so it never stalls the scheduler tick; building/running a detector is fail-closed (a broken detector never suppresses). No detectors → the flag is cleared and the loop short-circuits.

Source

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.

Trait Implementations§

Source§

impl Clone for Scheduler

Source§

fn clone(&self) -> Scheduler

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<S> FromSample<S> for S

§

fn from_sample_(s: S) -> S

§

impl<T> GetSetFdFlags for T

§

fn get_fd_flags(&self) -> Result<FdFlags, Error>
where T: AsFilelike,

Query the “status” flags for the self file descriptor.
§

fn new_set_fd_flags(&self, fd_flags: FdFlags) -> Result<SetFdFlags<T>, Error>
where T: AsFilelike,

Create a new SetFdFlags value for use with set_fd_flags. Read more
§

fn set_fd_flags(&mut self, set_fd_flags: SetFdFlags<T>) -> Result<(), Error>
where T: Sized + AsFilelike,

Set the “status” flags for the self file descriptor. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

§

fn into_sample(self) -> T

§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> Pointee for T

§

type Pointer = u32

§

fn debug( pointer: <T as Pointee>::Pointer, f: &mut Formatter<'_>, ) -> Result<(), Error>

§

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

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

§

fn to_sample_(self) -> U

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<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more