Effect

template<size_t MaxSources = 8>
class Effect : public RxESP32::ReactiveNode

Reactive side-effect that automatically re-executes when dependencies change.

Effect represents a side-effect function that runs in response to changes in its dependencies (Signal / Computed). Unlike Computed, Effects don’t produce a value - they perform actions like logging, updating UI, or communicating with hardware.

Since

v0.1.0

Key Features:

  • Automatic dependency tracking during effect execution.

  • Optional cleanup function for resource management.

  • Lazy or eager execution modes.

  • Can be suspended/resumed for temporary disabling.

  • Priority-based scheduling in dispatcher.

  • Thread-safe via FreeRTOS mutex.

Usage:

Signal<int> counter(0);

// Effect runs whenever counter changes
Effect logger([]() {
  Serial.println(counter.get()); // Register dependency with get()
  // Optional cleanup function, otherwise return nullptr
  return []() { Serial.println("Cleaning up"); };
});

counter.set(5);  // Prints: 5
counter.set(10); // Prints: "Cleaning up" then "10"

Template Parameters:
size_t MaxSources = 8

Maximum number of source dependencies to track.

Public Types

using CleanupFunction = std::function<void()>
using EffectFunction = std::function<CleanupFunction()>

Public Functions

inline explicit Effect(EffectFunction effect_function, const Options &options = {})

Construct Effect with effect function and options.

Creates a reactive effect that executes when dependencies change. By default, executes immediately in constructor to establish dependencies. Optionally returns a cleanup function to be called before next execution.

Since

v0.1.0

Signal<bool> ledState(false);

// Simple effect
Effect<> ledController([]() {
  digitalWrite(LED_PIN, ledState.get()); // Register dependency with get()
  return nullptr;  // No cleanup needed
});

// Effect with cleanup
Effect<> timer([]() {
  int interval = timer_interval.get();
  auto handle = startTimer(interval);
  return [=]() { stopTimer(handle); };  // Cleanup old timer
});

// Lazy effect (manual control)
Effect<> manual([]() {
  processData(data.get());
  return nullptr;
}, {.lazy = true});

// Manually trigger lazy effect
manual.run();

Note

  • With skip_initial_run = true, first execution deferred until first trigger.

  • With lazy = true, only executes on manual run() call.

Warning

If you don’t need a cleanup function, you must return nullptr.

Parameters:
EffectFunction effect_function

Function to execute (returns optional cleanup function).

const Options &options = {}

Configuration (name, priority, skip_initial_run, lazy).

inline ~Effect() override
inline virtual Type getType() const override

Get the runtime type of this node.

Since

v0.1.0

Returns:

Type::Effect.

inline Status suspend()

Suspend effect execution temporarily.

Prevents the effect from running when dependencies change. Changes are still tracked (effect remains dirty), but execution is deferred until resume(). Useful for temporarily disabling effects during batch updates.

Since

v0.1.0

Effect<> logger([]() {
  Serial.println(sensor.get());
  return nullptr;
});

logger.suspend();
// Make many changes without triggering effect
sensor.set(1);
sensor.set(2);
sensor.set(3);
logger.resume();  // Effect runs once with final value

Returns:

Status::Ok on success, error code otherwise.

inline Status resume()

Resume effect execution after suspend.

Re-enables effect execution. If effect became dirty while suspended, it will be dispatched immediately for execution.

Since

v0.1.0

Returns:

Status::Ok on success, error code otherwise.

inline bool isSuspended() const

Check if effect is currently suspended.

Since

v0.1.0

Returns:

true if suspended, false if active.

inline Status run()

Manually trigger effect execution.

Forces the effect to run immediately, regardless of dirty state. For lazy effects, this is the primary way to trigger execution. For normal effects, can be used to force re-execution.

Since

v0.1.0

// Lazy effect (doesn't auto-run)
Effect processor([]() {
  processData(dataQueue.get());
  return nullptr;
}, {.lazy = true});

// Manually trigger when needed
if (userButtonPressed) {
  processor.run();
}

Note

  • First call with option skip_initial_run triggers initial execution.

  • Useful for lazy effects that only run on manual trigger.

Returns:

Status::Ok on success, error code otherwise.

inline bool isDirty() const

Check if effect needs to run (dependencies changed).

Effect logger([]() {
  Serial.println(data.get());
  return nullptr;
});

if (logger.isDirty()) {
  Serial.println("Effect will run soon");
}
Since

v0.1.0

Returns:

true if dirty (needs execution), false if clean.

inline virtual const char *getName() const override

Get the name of this node.

Since

v0.1.0

Returns:

C-string name, or default “Node” if not set.

struct Options

Configuration options for Effect construction.

Since

v0.1.0

Public Members

const char *name = nullptr

Name for debugging (optional)

Priority priority = Priority::Normal

Execution priority.

bool skip_initial_run = false

Don’t execute effect in constructor.

bool lazy = false

Execute only on manual run() call.

See Also