Reactive tree node — each node has a type name, properties, and children. More...
#include <state_tree.hpp>
Classes | |
| class | Transaction |
| RAII transaction on a StateTree. More... | |
| class | ValidatedTransaction |
| A transaction whose edits are checked and return TreeEditResult instead of being silently ignored. More... | |
| class | SyncedClone |
| Owns the listener wiring created by StateTree::clone_synced(). More... | |
Public Types | |
| using | Ptr = std::shared_ptr<StateTree> |
Public Member Functions | |
| const std::string & | type_name () const |
| Type name (e.g., "preset", "oscillator", "filter"). | |
| void | set (std::string_view name, PropertyValue value) |
| Set a property value (notifies listeners). | |
| TreeEditResult | set_checked (std::string_view name, PropertyValue value, const PropertyValidator &validator={}) |
Set a property, returning an error value instead of applying an invalid edit: an empty name, or a value validator refuses. | |
| PropertyValue | get (std::string_view name) const |
| Get a property value (returns monostate if not set). | |
| bool | get_bool (std::string_view name, bool default_val=false) const |
| Typed getters with defaults. | |
| int64_t | get_int (std::string_view name, int64_t default_val=0) const |
| double | get_double (std::string_view name, double default_val=0.0) const |
| std::string | get_string (std::string_view name, std::string_view default_val="") const |
| bool | has (std::string_view name) const |
| Whether a property exists. | |
| void | remove (std::string_view name) |
| Remove a property. | |
| TreeEditResult | remove_checked (std::string_view name) |
| Remove a property; kMissingProperty if it does not exist. | |
| std::vector< std::string > | property_names () const |
| Get all property names (snapshot copy). | |
| void | add_child (Ptr child) |
| Add a child node (notifies child-added listeners). | |
| TreeEditResult | add_child_checked (Ptr child) |
| Checked add_child(): returns an error instead of ignoring the edit. | |
| void | insert_child (int index, Ptr child) |
| Insert a child at a specific index (clamped to the valid range). | |
| TreeEditResult | insert_child_checked (int index, Ptr child) |
| Checked insert_child(): an out-of-range index is an error, not clamped. | |
| void | remove_child (int index) |
| Remove a child by index (notifies child-removed listeners). | |
| TreeEditResult | remove_child_checked (int index) |
| Checked remove_child(int). | |
| void | remove_child (StateTree *child) |
| Remove a specific child. | |
| void | remove_all_children () |
| Remove every child. | |
| int | child_count () const |
| Number of children. | |
| Ptr | child (int index) const |
| Get child by index. | |
| int | index_of (const StateTree *node) const |
Index of node among this node's children, or -1. | |
| Ptr | find_child (std::string_view type_name) const |
| Find first child with matching type name. | |
| std::vector< Ptr > | find_children (std::string_view type_name) const |
| Find all children with matching type name. | |
| void | for_each_descendant (const std::function< void(StateTree &)> &fn) |
| Visit every descendant (not this node) depth-first, parents before children. | |
| Ptr | find_descendant (const std::function< bool(const StateTree &)> &predicate) const |
First descendant (depth-first, parents before children, not this node) for which predicate returns true, or null. | |
| bool | is_equivalent_to (const StateTree &other) const |
| Structural equality: same type name, equal properties (exact variant match, so int64 3 differs from double 3.0) and equivalent children in the same order. | |
| StateTree * | parent () const |
| Parent (may be null for root). | |
| Transaction | begin_transaction () |
| Open (or join) a transaction on this node's tree. | |
| Transaction | begin_transaction (UndoManager *undo, std::string_view name) |
Open (or join) a transaction that records inverse edits so the whole transaction becomes one named step in undo when the outermost transaction commits. | |
| ValidatedTransaction | begin_validated_transaction (PropertyValidator validator={}, UndoManager *undo=nullptr, std::string_view name={}) |
| Open (or join) a transaction whose edits are checked and return TreeEditResult. | |
| bool | in_transaction () const |
| Whether a transaction is open that covers edits made through this node. | |
| int | add_listener (TreeListener listener, ListenerDelivery delivery=ListenerDelivery::kCoalesced) |
| Listen for property changes. | |
| void | remove_listener (int id) |
| int | add_child_added_listener (ChildListener listener, ListenerDelivery delivery=ListenerDelivery::kCoalesced) |
| Listen for child added/removed. | |
| int | add_child_removed_listener (ChildListener listener, ListenerDelivery delivery=ListenerDelivery::kCoalesced) |
| void | remove_child_added_listener (int id) |
| void | remove_child_removed_listener (int id) |
| int | add_child_moved_listener (ChildMovedListener listener, ListenerDelivery delivery=ListenerDelivery::kCoalesced) |
| Listen for reorders within this node (move_child with one parent). | |
| void | remove_child_moved_listener (int id) |
| int | add_parent_changed_listener (ParentChangedListener listener, ListenerDelivery delivery=ListenerDelivery::kCoalesced) |
| Listen for this node's parent changing. | |
| void | remove_parent_changed_listener (int id) |
| int | add_ancestor_changed_listener (AncestorChangedListener listener, ListenerDelivery delivery=ListenerDelivery::kCoalesced) |
| Listen for any ancestor link above this node changing. | |
| void | remove_ancestor_changed_listener (int id) |
| std::string | to_json () const |
| Serialize to JSON string. | |
| Ptr | deep_copy () const |
| Deep-copy this subtree (including properties and recursive children). | |
| SyncedClone | clone_synced () |
Static Public Member Functions | |
| static Ptr | create (std::string type_name) |
| Create a tree node with a type name. | |
| static bool | move_child (StateTree &from_parent, int from_index, StateTree &to_parent, int to_index) |
Move the child at from_index of from_parent to to_index of to_parent. | |
| static TreeEditResult | move_child_checked (StateTree &from_parent, int from_index, StateTree &to_parent, int to_index) |
| Checked move_child(). | |
| static Ptr | from_json (std::string_view json) |
| Deserialize from JSON string. | |
Reactive tree node — each node has a type name, properties, and children.
Thread-safety contract: StateTree is not thread-safe. Every public method below — including const accessors like get() and property_names() — touches non-atomic members (properties_, children_, listener vectors) without internal locking. Callers must serialize all access from a single thread, or provide their own external mutex.
The intended usage model is "main-thread / UI-thread only" (audio threads should use the lock-free StateStore for parameter exchange instead). The <mutex> header is included as a forward-compatibility hook for callers that wrap an external std::scoped_lock around a StateTree handle; the class itself holds none. Transactions follow the same rule: begin, commit and abort on the thread that owns the tree.
Mutating a StateTree from one thread while another thread reads it is a data race even when only "harmless"-looking const methods are involved (e.g. property_names() copies a std::map while a writer may rehash it). The TSan regression in test_state_tree.cpp pins this contract.
Listener contract:
Transactions and reentrancy:
| using pulp::state::StateTree::Ptr = std::shared_ptr<StateTree> |
| int pulp::state::StateTree::add_ancestor_changed_listener | ( | AncestorChangedListener | listener, |
| ListenerDelivery | delivery = ListenerDelivery::kCoalesced ) |
Listen for any ancestor link above this node changing.
NOT thread-safe — caller must serialize.
| void pulp::state::StateTree::add_child | ( | Ptr | child | ) |
Add a child node (notifies child-added listeners).
A child that already has a parent is moved here (child_removed on the old parent, then child_added here, then parent_changed). Edits that would create a cycle, or span two trees with different open transactions, are ignored; use add_child_checked() to get the reason. NOT thread-safe — caller must serialize. Mutates children_ and the child's parent_ back-pointer.
| int pulp::state::StateTree::add_child_added_listener | ( | ChildListener | listener, |
| ListenerDelivery | delivery = ListenerDelivery::kCoalesced ) |
Listen for child added/removed.
NOT thread-safe — caller must serialize.
| TreeEditResult pulp::state::StateTree::add_child_checked | ( | Ptr | child | ) |
Checked add_child(): returns an error instead of ignoring the edit.
NOT thread-safe — message thread only.
| int pulp::state::StateTree::add_child_moved_listener | ( | ChildMovedListener | listener, |
| ListenerDelivery | delivery = ListenerDelivery::kCoalesced ) |
Listen for reorders within this node (move_child with one parent).
NOT thread-safe — caller must serialize.
| int pulp::state::StateTree::add_child_removed_listener | ( | ChildListener | listener, |
| ListenerDelivery | delivery = ListenerDelivery::kCoalesced ) |
| int pulp::state::StateTree::add_listener | ( | TreeListener | listener, |
| ListenerDelivery | delivery = ListenerDelivery::kCoalesced ) |
Listen for property changes.
NOT thread-safe — caller must serialize. Listener vectors are not guarded; registering or removing while a notify is in flight on another thread is a data race.
| int pulp::state::StateTree::add_parent_changed_listener | ( | ParentChangedListener | listener, |
| ListenerDelivery | delivery = ListenerDelivery::kCoalesced ) |
Listen for this node's parent changing.
NOT thread-safe — caller must serialize.
|
nodiscard |
Open (or join) a transaction on this node's tree.
See the class comment for the full contract. NOT thread-safe — message thread only.
|
nodiscard |
Open (or join) a transaction that records inverse edits so the whole transaction becomes one named step in undo when the outermost transaction commits.
The first manager supplied while the transaction is open wins; a later one passed by a joining transaction is ignored. An aborted or empty transaction pushes no step. NOT thread-safe — message thread only; undo must outlive the commit.
|
nodiscard |
Open (or join) a transaction whose edits are checked and return TreeEditResult.
validator (optional) vets every property value. NOT thread-safe — message thread only.
| Ptr pulp::state::StateTree::child | ( | int | index | ) | const |
Get child by index.
NOT thread-safe — caller must serialize.
|
inline |
Number of children.
NOT thread-safe — caller must serialize. std::vector::size is non- atomic relative to insert/remove.
| SyncedClone pulp::state::StateTree::clone_synced | ( | ) |
|
inlinestatic |
Create a tree node with a type name.
Thread-safe (only touches the local arguments).
| Ptr pulp::state::StateTree::deep_copy | ( | ) | const |
Deep-copy this subtree (including properties and recursive children).
NOT thread-safe — caller must serialize with any concurrent mutator of *this. The returned copy is itself a fresh tree.
| Ptr pulp::state::StateTree::find_child | ( | std::string_view | type_name | ) | const |
Find first child with matching type name.
NOT thread-safe — caller must serialize.
| std::vector< Ptr > pulp::state::StateTree::find_children | ( | std::string_view | type_name | ) | const |
Find all children with matching type name.
NOT thread-safe — caller must serialize.
| Ptr pulp::state::StateTree::find_descendant | ( | const std::function< bool(const StateTree &)> & | predicate | ) | const |
First descendant (depth-first, parents before children, not this node) for which predicate returns true, or null.
NOT thread-safe — message thread only.
| void pulp::state::StateTree::for_each_descendant | ( | const std::function< void(StateTree &)> & | fn | ) |
Visit every descendant (not this node) depth-first, parents before children.
A node's child list is snapshotted right after fn has visited that node, so fn may edit the tree: children it removes from the node being visited are skipped, siblings already snapshotted are still visited even if removed later. NOT thread-safe — message thread only.
|
static |
Deserialize from JSON string.
Returns a freshly created tree, so the call itself is thread-safe; the returned StateTree inherits the same "caller serializes" contract for subsequent access. Thread-safe (returns a new instance owned by the caller).
| PropertyValue pulp::state::StateTree::get | ( | std::string_view | name | ) | const |
Get a property value (returns monostate if not set).
NOT thread-safe — caller must serialize. std::map::find is a non- atomic read of the tree's internal nodes.
| bool pulp::state::StateTree::get_bool | ( | std::string_view | name, |
| bool | default_val = false ) const |
Typed getters with defaults.
NOT thread-safe — caller must serialize. Delegate to get().
| double pulp::state::StateTree::get_double | ( | std::string_view | name, |
| double | default_val = 0.0 ) const |
| int64_t pulp::state::StateTree::get_int | ( | std::string_view | name, |
| int64_t | default_val = 0 ) const |
| std::string pulp::state::StateTree::get_string | ( | std::string_view | name, |
| std::string_view | default_val = "" ) const |
| bool pulp::state::StateTree::has | ( | std::string_view | name | ) | const |
Whether a property exists.
NOT thread-safe — caller must serialize.
| bool pulp::state::StateTree::in_transaction | ( | ) | const |
Whether a transaction is open that covers edits made through this node.
NOT thread-safe — caller must serialize.
| int pulp::state::StateTree::index_of | ( | const StateTree * | node | ) | const |
Index of node among this node's children, or -1.
NOT thread-safe — caller must serialize.
| void pulp::state::StateTree::insert_child | ( | int | index, |
| Ptr | child ) |
Insert a child at a specific index (clamped to the valid range).
NOT thread-safe — caller must serialize.
| TreeEditResult pulp::state::StateTree::insert_child_checked | ( | int | index, |
| Ptr | child ) |
Checked insert_child(): an out-of-range index is an error, not clamped.
NOT thread-safe — message thread only.
| bool pulp::state::StateTree::is_equivalent_to | ( | const StateTree & | other | ) | const |
Structural equality: same type name, equal properties (exact variant match, so int64 3 differs from double 3.0) and equivalent children in the same order.
Listeners and transaction state are not compared. NOT thread-safe — caller must serialize with mutators of both trees.
|
static |
Move the child at from_index of from_parent to to_index of to_parent.
to_index is the child's final position: for a reorder within one parent it indexes the list after the child is taken out. Same parent: notifies child_moved. Different parent: notifies child_removed, child_added, parent_changed and ancestor_changed. Returns false (and changes nothing) if the move is invalid. NOT thread-safe — message thread only; both parents must be owned by the calling thread.
|
static |
Checked move_child().
NOT thread-safe — message thread only.
|
inline |
Parent (may be null for root).
NOT thread-safe — caller must serialize. The raw back-pointer is rewritten by add_child / insert_child / remove_child / move_child.
| std::vector< std::string > pulp::state::StateTree::property_names | ( | ) | const |
| void pulp::state::StateTree::remove | ( | std::string_view | name | ) |
Remove a property.
NOT thread-safe — caller must serialize. Touches properties_ and invokes property-changed listeners with the removed value.
| void pulp::state::StateTree::remove_all_children | ( | ) |
Remove every child.
All children are detached first, then child_removed / parent_changed fire from the last child backwards. NOT thread-safe — caller must serialize.
| void pulp::state::StateTree::remove_ancestor_changed_listener | ( | int | id | ) |
| TreeEditResult pulp::state::StateTree::remove_checked | ( | std::string_view | name | ) |
Remove a property; kMissingProperty if it does not exist.
NOT thread-safe — message thread only.
| void pulp::state::StateTree::remove_child | ( | int | index | ) |
Remove a child by index (notifies child-removed listeners).
NOT thread-safe — caller must serialize.
| void pulp::state::StateTree::remove_child | ( | StateTree * | child | ) |
Remove a specific child.
NOT thread-safe — caller must serialize.
| void pulp::state::StateTree::remove_child_added_listener | ( | int | id | ) |
| TreeEditResult pulp::state::StateTree::remove_child_checked | ( | int | index | ) |
Checked remove_child(int).
NOT thread-safe — message thread only.
| void pulp::state::StateTree::remove_child_moved_listener | ( | int | id | ) |
| void pulp::state::StateTree::remove_child_removed_listener | ( | int | id | ) |
| void pulp::state::StateTree::remove_listener | ( | int | id | ) |
| void pulp::state::StateTree::remove_parent_changed_listener | ( | int | id | ) |
| void pulp::state::StateTree::set | ( | std::string_view | name, |
| PropertyValue | value ) |
Set a property value (notifies listeners).
NOT thread-safe — caller must serialize. Touches properties_ and invokes every registered listener inline (or queues the coalesced ones when a transaction is open).
| TreeEditResult pulp::state::StateTree::set_checked | ( | std::string_view | name, |
| PropertyValue | value, | ||
| const PropertyValidator & | validator = {} ) |
Set a property, returning an error value instead of applying an invalid edit: an empty name, or a value validator refuses.
Intended for edits that originate from scripts or remote tools. NOT thread-safe — message thread only.
| std::string pulp::state::StateTree::to_json | ( | ) | const |
Serialize to JSON string.
NOT thread-safe — caller must serialize. Walks every property and child of the subtree.
|
inline |
Type name (e.g., "preset", "oscillator", "filter").
NOT thread-safe — caller must serialize with any concurrent mutator of the same node. Reading type_name_ while another thread is destroying or reassigning the node races on the std::string body.