Computed

template<typename T, size_t MaxSources = 8, size_t MaxDeps = 8, typename Filter = std::not_equal_to<T>>
class Computed : public RxESP32::ReactiveNode

Reactive computed value that automatically updates when dependencies change.

Computed represents a derived value that is automatically recalculated when any of its source Signal or other Computed change. The compute function is executed eagerly (default) or lazily (on-demand), depending on the configuration. Dependencies are tracked automatically during the execution of the compute function. Compute function is executed in dependency tracking context (dispatcher task).

Since

v0.1.0

Key Features:

  • Automatic dependency tracking during compute function execution.

  • Lazy or eager evaluation modes.

  • Caching to avoid redundant computations.

  • Priority-based scheduling in dispatcher.

  • Thread-safe via FreeRTOS mutex.

  • Optional skip_initial_run for deferred initialization.

Signal<int> a(5);
Signal<int> b(10);

// Auto-tracks dependencies on a and b
Computed<int> sum([]() {
  return a.get() + b.get();
});

Serial.println(sum.get()); // 15
a.set(20); // sum automatically recomputes to 30 on dispatcher run
// ... a couple of dispatcher cycles later ...
Serial.println(sum.get()); // 30
Template Parameters:
typename T

Type of the computed value.

size_t MaxSources = 8

Maximum number of source dependencies to track.

size_t MaxDeps = 8

Maximum number of dependents that can subscribe.

typename Filter = std::not_equal_to<T>

Filter for change detection.

Public Types

using ComputeFunction = std::function<T()>

Public Functions

inline explicit Computed(ComputeFunction compute_function, const Options &options = {})

Construct Computed with compute function and options.

Creates a reactive computed value. By default, executes the compute function immediately in constructor to establish dependencies and compute initial value.

Since

v0.1.0

Signal<int> x(5);

// Eager computation (default), value computed immediately (= 10)
Computed<int> doubled([]() { return x.get() * 2; });

// Lazy computation (only when accessed), recomputed on-demand
// Initial value not computed until first get() (initializes to T() )
Computed<int> squared([]() { return pow(x.get(), 2); },
  {.lazy = true});

// Skip initial run
Computed<int> negated([]() {
  return -x.get();
}, {.skip_initial_run = true});

Note

  • With skip_initial_run = true, first execution deferred until first get().

  • With lazy = true, only recomputes and notify dependents on get() when dirty (saves CPU).

Parameters:
ComputeFunction compute_function

Function that computes the derived value.

const Options &options = {}

Configuration (name, priority, skip_initial_run, lazy, skip_filter_check).

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

Get the runtime type of this node.

Since

v0.1.0

Returns:

Type::Computed.

inline T get()

Get current computed value.

Returns the cached value if clean, or recomputes if dirty. Automatically registers caller as dependent if called from within another Computed or Effect. For lazy computeds, triggers recomputation on-demand when dirty.

Since

v0.1.0

Signal<int> x(5);
Computed<int> doubled([]() { return x.get() * 2; });

int value = doubled.get();  // 10
x.set(10);
value = doubled.get();      // 20 (auto-recomputed)

Note

First call with option skip_initial_run triggers initial computation.

Returns:

Current computed value.

inline T operator()()

Function call operator for convenient access.

Equivalent to get().

Since

v0.1.0

Returns:

Current computed value.

inline T peek()

Read value without dependency tracking.

Returns computed value without registering caller as dependent.

Since

v0.1.0

Returns:

Current computed value.

inline Status invalidate(bool notify_dependents = true)

Manually invalidate cached value and optionally notify dependents.

Marks the computed as dirty and optionally triggers recomputation and notification of dependents. Useful for forcing re-evaluation or implementing custom invalidation logic.

Since

v0.1.0

Computed<bool> status([]() {
  return sensor.isReady();
});

// Force re-evaluation (e.g., after external state change)
status.invalidate();

// Invalidate without notifying (silent update)
status.invalidate(false);

Note

  • For lazy computeds, marks dirty but doesn’t trigger recomputation until get().

  • For eager computeds, instantly enqueues for recomputation.

Parameters:
bool notify_dependents = true

If true (default), notify dependents after invalidation.

Returns:

Status::Ok on success, error code otherwise.

inline bool isDirty() const

Check if computed value is stale (needs recomputation).

Computed<int> sum([]() { return a.get() + b.get(); });

if (sum.isDirty()) {
  Serial.println("Sum needs recomputation");
}
Since

v0.1.0

Returns:

true if dirty (value is stale), false if clean (cached value is valid).

inline virtual const char *getName() const override

Get the name of this node.

Since

v0.1.0

Returns:

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

struct Options

Configuration options for Computed 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 compute function in constructor.

bool lazy = false

Evaluate only on get() (on-demand mode)

bool skip_filter_check = false

Force propagation even if Filter returns false.

See Also