Skip to content

Scheduler

Source: Scheduler.h

Scheduler

class Scheduler
src/core/Scheduler.h:47

Domain-neutral orchestrator that owns the top-level module list, runs the 4-phase boot, drives the per-tick loop callbacks, and provides tree-walk utilities (delete, name-uniquify).

Boot phases: see the setup() comment in Scheduler.cpp.

Tick: gates each top-level module by enabled() / respectsEnabled() and dispatches tick / tick20ms / tick1s. A per-second window averages the tick time and publishes each module's loop time.

Loop rates: three cadences cover every module. tick() is the hot path for effects and drivers, called every iteration — the Scheduler handles pacing (yielding to other tasks between iterations via taskYIELD() on ESP32, an optional sleep on desktop). tick20ms() runs every ~20 ms for UI updates, control reads, and network polling. tick1s() runs every ~1 second for diagnostics, reconnects, and housekeeping. Not every module needs tick(): system modules (HTTP, WiFi) use tick20ms() or tick1s() only.

Timing: effects animate off a synchronized clock (millis from the platform), so the visual speed is frame-rate independent — the same at 30 fps and 60 fps. For multi-device sync a leader synchronizes this clock across devices; no frame counter is needed.

Execution model:tick() runs every top-level module inline, in one loop, in declared order; each module then drives its own children. There is no per-module task and no core-affinity field — a single render loop, not a task-per-module fan-out.

Module ordering: child modules run in their declared order within the parent, and top-level modules also run in declared order. The UI supports reordering, backed by the scheduler. Relationships are parent/child only — there is no arbitrary dependency graph.

Prior art: MoonLight's effectTask / svelteTask — two FreeRTOS tasks (effects on core 1, system/drivers on core 0) with a per-node tick() every frame and a tick20ms() for slow updates.

This is the .h interface; bodies live in Scheduler.cpp.

Public Methods

inline void setLoadAllHook(LoadAllFn fn)

inline void setNoteDirtyHook(NoteDirtyFn fn)

void addModule(MoonModule * mod)

void setup()

void tick()

void release()

uint32_t elapsed() const

void prepareTree()

inline uint32_t tickTimeUs() const

inline uint32_t fps() const

inline uint8_t moduleCount() const

inline MoonModule * module(uint8_t i) const

void ensureUniqueName(MoonModule * mod) : Ensure mod's name is tree-globally unique.

void deduplicateNamesInTree() : Walk the whole tree and disambiguate every duplicated name.

MoonModule * firstByName(constchar * name) : First module in tree-walk order with this name, or nullptr if none.

SetControlResult setControl(constchar * moduleName, constchar * controlName, constchar * valueJson) : Set one control by (module name, control name) to a value, applying the full control-change reaction: parse+validate, rebuild the module's control list, fire onControlChanged, mark dirty for persistence, and prepareTree() when the control reshapes dims/mapping.

Public Static Methods

static void deleteTree(MoonModule * mod)

static inline Scheduler * instance() : The single live Scheduler, or nullptr before setup() / after release().

Public Types

enum SetControlResult : Outcome of setControl — the generic control-set primitive's result.

Value Description
Ok
ModuleNotFound no module with that name in the tree
ControlNotFound module exists but has no such control
OutOfRange numeric value outside the control's bounds
Malformed value didn't parse
ReadOnly tried to write a display-only control

using LoadAllFn = void()(Scheduler ) : Function-pointer hook invoked between phase 1 (defineControls) and phase 3 (setup).

using NoteDirtyFn = void(*)() : Hook invoked after a control mutation so the persistence layer can schedule a debounced save (FilesystemModule::noteDirty).