Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
aria::binding::BindingEngine Class Reference

BindingEngine: connects ViewModel properties to platform views via an adapter. More...

#include <binding_engine.hpp>

Public Types

enum class  DispatchPolicy { Direct , SmartMarshal , AlwaysPost }
 Binding dispatch policy — see the class header for semantics. More...

Public Member Functions

 BindingEngine (std::shared_ptr< IViewAdapter > adapter)
 Convenience constructor: no dispatcher, Direct policy.
 BindingEngine (std::shared_ptr< IViewAdapter > adapter, std::shared_ptr< runtime::IDispatcher > ui_dispatcher, DispatchPolicy policy=DispatchPolicy::SmartMarshal)
 Constructor that opts into a dispatch policy.
 ~BindingEngine ()
 Retires all lifetime gates before releasing the bindings.
 BindingEngine (const BindingEngine &)=delete
BindingEngineoperator= (const BindingEngine &)=delete
 BindingEngine (BindingEngine &&)=delete
BindingEngineoperator= (BindingEngine &&)=delete
IViewAdapteradapter () noexcept
DispatchPolicy dispatch_policy () const noexcept
bool has_dispatcher () const noexcept
void bind_text_oneway (Property< std::string > &prop, IView &view)
template<ReadOnlyReactiveOf< std::string > Src>
void bind_text_oneway (Src &src, IView &view)
void bind_text (Property< std::string > &prop, IView &view)
void bind_bool_oneway (Property< bool > &prop, IView &view)
template<ReadOnlyReactiveOf< bool > Src>
void bind_bool_oneway (Src &src, IView &view)
void bind_bool (Property< bool > &prop, IView &view)
void bind_int_oneway (Property< int > &prop, IView &view)
template<ReadOnlyReactiveOf< int > Src>
void bind_int_oneway (Src &src, IView &view)
void bind_int (Property< int > &prop, IView &view)
void bind_int64_oneway (Property< std::int64_t > &prop, IView &view)
template<ReadOnlyReactiveOf< std::int64_t > Src>
void bind_int64_oneway (Src &src, IView &view)
void bind_int64 (Property< std::int64_t > &prop, IView &view)
void bind_uint64_oneway (Property< std::uint64_t > &prop, IView &view)
template<ReadOnlyReactiveOf< std::uint64_t > Src>
void bind_uint64_oneway (Src &src, IView &view)
void bind_uint64 (Property< std::uint64_t > &prop, IView &view)
void bind_float_oneway (Property< float > &prop, IView &view)
template<ReadOnlyReactiveOf< float > Src>
void bind_float_oneway (Src &src, IView &view)
void bind_float (Property< float > &prop, IView &view)
void bind_double_oneway (Property< double > &prop, IView &view)
template<ReadOnlyReactiveOf< double > Src>
void bind_double_oneway (Src &src, IView &view)
void bind_double (Property< double > &prop, IView &view)
void bind_visible (Property< bool > &prop, IView &view)
template<ReadOnlyReactiveOf< bool > Src>
void bind_visible (Src &src, IView &view)
void bind_enabled (Property< bool > &prop, IView &view)
template<ReadOnlyReactiveOf< bool > Src>
void bind_enabled (Src &src, IView &view)
template<ReadOnlyReactive Src>
void bind_text_converted_oneway (Src &src, IView &view, Converter< typename Src::value_type, std::string > conv)
template<typename T>
void bind_text_converted (Property< T > &prop, IView &view, Converter< T, std::string > conv)
template<typename T>
void bind_int_converted (Property< T > &prop, IView &view, Converter< T, int > conv)
 Bind a model value to an integer-valued control.
template<ReadOnlyReactive Src, typename Project>
void bind_text_projected (Src &src, IView &view, Project project)
 Bind a read-only text view to src, rendered through project (T -> std::string).
template<ReadOnlyReactiveOptional Src, typename Project>
void bind_optional_text (Src &src, IView &view, Project project, std::string empty_text=std::string{})
 Bind a read-only text view to a reactive std::optional<T> source.
template<typename... Args>
void bind_command (Command< Args... > &cmd, IView &view, const Args &... args)
void bind_view_lifetime (IView &view, std::function< void()> on_view_destroyed)
void adopt (IView &view, Subscription s)
 Adopt an arbitrary Subscription into view's per-view bucket.
void clear () noexcept
 Drop every active binding.

Detailed Description

BindingEngine: connects ViewModel properties to platform views via an adapter.

── Lifetime contract ─────────────────────────────────────────────────

  • The typical ownership shape is that the platform widget tree owns every IView (QWidget parent-owned, NSView superview-owned, ...) and the BindingEngine is a member of the corresponding ViewModel / scope. Either side may outlive the other:
    • If the engine is destroyed first (normal scope exit), all bindings are released and the views are untouched.
    • If a view is destroyed first, every binding wired to that view is released automatically via IView::on_destroy, so subsequent property changes do not dereference the dead view. Other views bound to the same engine keep working.
  • Destroying the BindingEngine (or calling clear()) releases every active binding in one shot.

── One-way vs two-way naming ───────────────────────────────────────── Controls split into two families:

── Threading / binding dispatch policy ────────────────────────────── Native UI toolkits are main-thread-affine: AppKit/UIKit explicitly forbid touching NS/UIView from a background thread, and Qt requires widget access on the GUI thread. Aria's reactive graph is itself single-threaded. BindingEngine accepts an optional dispatcher for that owning thread and a policy applied in both binding directions. In particular, adapters such as HTTP may deliver input from workers; marshalling keeps those callbacks from accessing the graph there. Application Property writes must still respect graph affinity.

  • DispatchPolicy::Direct (default) — every callback is invoked synchronously on its originating thread. Use when every Property write and adapter callback originates on the graph/UI thread (the common single-threaded MVVM case).
  • DispatchPolicy::SmartMarshal (recommended for production) — VM→View setters, View→VM edits and commands are invoked directly when dispatcher.is_main_thread() is true, otherwise posted to the dispatcher. Zero overhead on the UI thread, and a guaranteed thread-correct path on background threads.
  • DispatchPolicy::AlwaysPost — every binding update is posted, even from the UI thread. Useful for tests that want a deterministic "property-emit happens before, view-update happens later" ordering, or to coalesce a synchronous burst of writes into the next event-loop iteration.

Initial synchronization during bind runs inline on the owning thread. Synchronous setter echoes are suppressed before posting. Binding setup, clear and view/engine destruction also belong on the owning thread; the dispatcher does not make the graph or the binding registry safe for concurrent access.

Posted callbacks in both directions use a per-view lifetime token: if the view is destroyed between dispatcher.post(fn) and fn() running, the bucket's weak handle no-ops the call so the posted lambda never dereferences a dead IView.

MSVC C4251: BindingEngine contains template methods that inline-access private STL members (shared_ptr, unordered_map). Full Pimpl would require explicit template instantiation for every bind_* variant, adding maintenance burden with no real ABI benefit — the class is always consumed through its non-template public API, and the shared_ptr members point to DLL-exported interfaces. Suppression is safe.

Member Enumeration Documentation

◆ DispatchPolicy

Binding dispatch policy — see the class header for semantics.

Enumerator
Direct 

call inline (default)

SmartMarshal 

inline iff dispatcher.is_main_thread()

AlwaysPost 

always post, even from the UI thread

Constructor & Destructor Documentation

◆ BindingEngine() [1/4]

aria::binding::BindingEngine::BindingEngine ( std::shared_ptr< IViewAdapter > adapter)
explicit

Convenience constructor: no dispatcher, Direct policy.

◆ BindingEngine() [2/4]

aria::binding::BindingEngine::BindingEngine ( std::shared_ptr< IViewAdapter > adapter,
std::shared_ptr< runtime::IDispatcher > ui_dispatcher,
DispatchPolicy policy = DispatchPolicy::SmartMarshal )

Constructor that opts into a dispatch policy.

ui_dispatcher may be null only when policy == Direct.

◆ ~BindingEngine()

aria::binding::BindingEngine::~BindingEngine ( )

Retires all lifetime gates before releasing the bindings.

Defined out of line so teardown code is emitted once inside the library.

◆ BindingEngine() [3/4]

aria::binding::BindingEngine::BindingEngine ( const BindingEngine & )
delete

◆ BindingEngine() [4/4]

aria::binding::BindingEngine::BindingEngine ( BindingEngine && )
delete

Member Function Documentation

◆ operator=() [1/2]

BindingEngine & aria::binding::BindingEngine::operator= ( const BindingEngine & )
delete

◆ operator=() [2/2]

BindingEngine & aria::binding::BindingEngine::operator= ( BindingEngine && )
delete

◆ adapter()

IViewAdapter & aria::binding::BindingEngine::adapter ( )
inlinenodiscardnoexcept

◆ dispatch_policy()

DispatchPolicy aria::binding::BindingEngine::dispatch_policy ( ) const
inlinenodiscardnoexcept

◆ has_dispatcher()

bool aria::binding::BindingEngine::has_dispatcher ( ) const
inlinenodiscardnoexcept

◆ bind_text_oneway() [1/2]

void aria::binding::BindingEngine::bind_text_oneway ( Property< std::string > & prop,
IView & view )

◆ bind_text_oneway() [2/2]

template<ReadOnlyReactiveOf< std::string > Src>
void aria::binding::BindingEngine::bind_text_oneway ( Src & src,
IView & view )
inline

◆ bind_text()

void aria::binding::BindingEngine::bind_text ( Property< std::string > & prop,
IView & view )

◆ bind_bool_oneway() [1/2]

void aria::binding::BindingEngine::bind_bool_oneway ( Property< bool > & prop,
IView & view )

◆ bind_bool_oneway() [2/2]

template<ReadOnlyReactiveOf< bool > Src>
void aria::binding::BindingEngine::bind_bool_oneway ( Src & src,
IView & view )
inline

◆ bind_bool()

void aria::binding::BindingEngine::bind_bool ( Property< bool > & prop,
IView & view )

◆ bind_int_oneway() [1/2]

void aria::binding::BindingEngine::bind_int_oneway ( Property< int > & prop,
IView & view )

◆ bind_int_oneway() [2/2]

template<ReadOnlyReactiveOf< int > Src>
void aria::binding::BindingEngine::bind_int_oneway ( Src & src,
IView & view )
inline

◆ bind_int()

void aria::binding::BindingEngine::bind_int ( Property< int > & prop,
IView & view )

◆ bind_int64_oneway() [1/2]

void aria::binding::BindingEngine::bind_int64_oneway ( Property< std::int64_t > & prop,
IView & view )

◆ bind_int64_oneway() [2/2]

template<ReadOnlyReactiveOf< std::int64_t > Src>
void aria::binding::BindingEngine::bind_int64_oneway ( Src & src,
IView & view )
inline

◆ bind_int64()

void aria::binding::BindingEngine::bind_int64 ( Property< std::int64_t > & prop,
IView & view )

◆ bind_uint64_oneway() [1/2]

void aria::binding::BindingEngine::bind_uint64_oneway ( Property< std::uint64_t > & prop,
IView & view )

◆ bind_uint64_oneway() [2/2]

template<ReadOnlyReactiveOf< std::uint64_t > Src>
void aria::binding::BindingEngine::bind_uint64_oneway ( Src & src,
IView & view )
inline

◆ bind_uint64()

void aria::binding::BindingEngine::bind_uint64 ( Property< std::uint64_t > & prop,
IView & view )

◆ bind_float_oneway() [1/2]

void aria::binding::BindingEngine::bind_float_oneway ( Property< float > & prop,
IView & view )

◆ bind_float_oneway() [2/2]

template<ReadOnlyReactiveOf< float > Src>
void aria::binding::BindingEngine::bind_float_oneway ( Src & src,
IView & view )
inline

◆ bind_float()

void aria::binding::BindingEngine::bind_float ( Property< float > & prop,
IView & view )

◆ bind_double_oneway() [1/2]

void aria::binding::BindingEngine::bind_double_oneway ( Property< double > & prop,
IView & view )

◆ bind_double_oneway() [2/2]

template<ReadOnlyReactiveOf< double > Src>
void aria::binding::BindingEngine::bind_double_oneway ( Src & src,
IView & view )
inline

◆ bind_double()

void aria::binding::BindingEngine::bind_double ( Property< double > & prop,
IView & view )

◆ bind_visible() [1/2]

void aria::binding::BindingEngine::bind_visible ( Property< bool > & prop,
IView & view )

◆ bind_visible() [2/2]

template<ReadOnlyReactiveOf< bool > Src>
void aria::binding::BindingEngine::bind_visible ( Src & src,
IView & view )
inline

◆ bind_enabled() [1/2]

void aria::binding::BindingEngine::bind_enabled ( Property< bool > & prop,
IView & view )

◆ bind_enabled() [2/2]

template<ReadOnlyReactiveOf< bool > Src>
void aria::binding::BindingEngine::bind_enabled ( Src & src,
IView & view )
inline

◆ bind_text_converted_oneway()

template<ReadOnlyReactive Src>
void aria::binding::BindingEngine::bind_text_converted_oneway ( Src & src,
IView & view,
Converter< typename Src::value_type, std::string > conv )
inline

◆ bind_text_converted()

template<typename T>
void aria::binding::BindingEngine::bind_text_converted ( Property< T > & prop,
IView & view,
Converter< T, std::string > conv )
inline

◆ bind_int_converted()

template<typename T>
void aria::binding::BindingEngine::bind_int_converted ( Property< T > & prop,
IView & view,
Converter< T, int > conv )
inline

Bind a model value to an integer-valued control.

The converter defines valid inputs; return nullopt to reject an unselected index. Rejections and converter exceptions preserve the model and report through "binding.converter". Dispatch and echo suppression match text.

◆ bind_text_projected()

template<ReadOnlyReactive Src, typename Project>
void aria::binding::BindingEngine::bind_text_projected ( Src & src,
IView & view,
Project project )
inline

Bind a read-only text view to src, rendered through project (T -> std::string).

One-way (VM→View) only. The initial value is synced inline on the calling (UI) thread; subsequent changes go through the configured dispatch policy and are dropped safely if the view is destroyed in flight.

src may be a Property<T> or a Computed<T> — a formatted derived value ("¥ 12.34" off a Computed<double>) is the archetypal case and needs no intermediate mirror property.

◆ bind_optional_text()

template<ReadOnlyReactiveOptional Src, typename Project>
void aria::binding::BindingEngine::bind_optional_text ( Src & src,
IView & view,
Project project,
std::string empty_text = std::string{} )
inline

Bind a read-only text view to a reactive std::optional<T> source.

When the optional holds a value it is rendered through project (const T& -> std::string); when it is std::nullopt the view shows empty_text (default: empty string). One-way (VM→View) only.

This is the missing piece for AsyncCommand::last_result (Property<std::optional<R>>): binding a result label used to require a hand-written on_changed that unwrapped the optional. A Computed<std::optional<T>> works identically.

◆ bind_command()

template<typename... Args>
void aria::binding::BindingEngine::bind_command ( Command< Args... > & cmd,
IView & view,
const Args &... args )
inline

◆ bind_view_lifetime()

void aria::binding::BindingEngine::bind_view_lifetime ( IView & view,
std::function< void()> on_view_destroyed )
inline

◆ adopt()

void aria::binding::BindingEngine::adopt ( IView & view,
Subscription s )
inline

Adopt an arbitrary Subscription into view's per-view bucket.

The subscription is released when view is destroyed (its IView::on_destroy fires and the engine clears the bucket) OR when the engine itself is destroyed / cleared — whichever comes first. Exactly the same lifetime the bind_* calls already give their own internal subscriptions.

This is the escape hatch for anything the typed bind_* surface does not cover yet: a hand-written prop.on_changed(...), a platform signal, a Computed::on_changed(...). Without it every host has to invent its own per-view subscription store (and the common workaround — a process-global std::vector<Subscription> — leaks by construction).

auto sub = vm.total.on_changed([lbl](double v) { ... });
engine.adopt(*lbl, std::move(sub));   // released on view-destroy 

◆ clear()

void aria::binding::BindingEngine::clear ( )
noexcept

Drop every active binding.


The documentation for this class was generated from the following file: