MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
NodeUtils.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "NodeSpec.hpp"
4
5namespace MayaFlux::Nodes {
6
7class Node;
8class NodeContext;
9
10/**
11 * @typedef TypedHook
12 * @brief Callback function type for node processing events, parameterised on context type.
13 *
14 * Defaults to NodeContext so existing code is unaffected. Concrete node classes
15 * specialise this with their own context type for their domain-specific callbacks,
16 * eliminating the need for callers to cast inside the lambda.
17 *
18 * Example (base interface):
19 * ```cpp
20 * node->on_tick([](NodeContext& ctx) { ... });
21 * ```
22 * Example (concrete node):
23 * ```cpp
24 * phasor->on_phase_wrap([](GeneratorContext& ctx) { ... });
25 * ```
26 */
27template <typename ContextT = NodeContext>
28using TypedHook = std::function<void(ContextT&)>;
29
30/**
31 * @typedef NodeHook
32 * @brief Alias for TypedHook<NodeContext>.
33 *
34 * Preserved for backward compatibility. All existing NodeHook usage continues
35 * to compile without changes. Migrate incrementally to TypedHook<ConcreteContext>
36 * on a per-class basis.
37 */
39
40/**
41 * @typedef NodeCondition
42 * @brief Predicate function type for conditional callbacks.
43 *
44 * Evaluated each tick; the paired callback fires only when this returns true.
45 *
46 * Example:
47 * ```cpp
48 * node->on_tick_if(
49 * [](NodeContext& ctx) { return ctx.value > 0.8; },
50 * [](NodeContext& ctx) { ... }
51 * );
52 * ```
53 */
54using NodeCondition = std::function<bool(NodeContext&)>;
55
56/**
57 * @typedef HookId
58 * @brief Identifies one registration inside a HookList. Zero is never issued.
59 */
60using HookId = uint64_t;
61
62/**
63 * @struct HookEntry
64 * @brief One registration held by a HookList.
65 *
66 * key is the type of the callable the caller handed in, so removal by callable keeps
67 * working when the stored hook is an adapter. tag and params are interpreted by the
68 * list's owner (event kind, threshold, range, target).
69 */
70struct HookEntry {
71 HookId id {};
72 const std::type_info* key { &typeid(void) };
73 uint8_t tag {};
74 std::array<double, 2> params {};
76 std::optional<NodeCondition> condition;
77};
78
79/**
80 * @class HookList
81 * @brief Stores, fires and clears the hooks of one node channel.
82 *
83 * A HookList registers itself with the Node that owns it, so Node::remove_all_hooks and
84 * Node::unhook reach every list without any node class overriding them. Ids are unique
85 * within the owning node.
86 *
87 * Reading and writing are safe across threads without locks. The entries are an immutable
88 * snapshot that writers replace with a compare-exchange, so fire() and snapshot() never
89 * block and a hook may add or remove hooks, including itself, while it runs. A fire in
90 * progress finishes over the entries it started with. Replaced snapshots are freed once no
91 * reader holds one.
92 */
93class MAYAFLUX_API HookList {
94public:
95 /**
96 * @class Snapshot
97 * @brief The entries of a list at one instant, kept alive while this object exists.
98 */
99 class Snapshot {
100 public:
101 Snapshot() = default;
102 ~Snapshot();
103
104 Snapshot(const Snapshot&) = delete;
105 Snapshot& operator=(const Snapshot&) = delete;
106 Snapshot(Snapshot&& other) noexcept
107 : m_list(std::exchange(other.m_list, nullptr))
108 , m_entries(std::exchange(other.m_entries, nullptr))
109 {
110 }
112
113 [[nodiscard]] const HookEntry* begin() const { return m_entries ? m_entries->data() : nullptr; }
114 [[nodiscard]] const HookEntry* end() const { return m_entries ? m_entries->data() + m_entries->size() : nullptr; }
115 [[nodiscard]] size_t size() const { return m_entries ? m_entries->size() : 0; }
116 [[nodiscard]] bool empty() const { return size() == 0; }
117
118 private:
119 friend class HookList;
120
121 Snapshot(const HookList* list, const std::vector<HookEntry>* entries)
122 : m_list(list)
123 , m_entries(entries)
124 {
125 }
126
127 const HookList* m_list { nullptr };
128 const std::vector<HookEntry>* m_entries { nullptr };
129 };
130
131 explicit HookList(Node& owner);
132 ~HookList();
133
134 HookList(const HookList&) = delete;
135 HookList& operator=(const HookList&) = delete;
136 HookList(HookList&&) = delete;
138
139 /** @brief Appends a hook with no duplicate check and returns its id. */
140 HookId add(NodeHook hook, const std::type_info& key, uint8_t tag = 0, std::array<double, 2> params = {});
141
142 /** @brief Appends unless an entry with the same key and params exists, in which case that entry's id is returned. */
143 HookId add_unique(NodeHook hook, const std::type_info& key, uint8_t tag = 0, std::array<double, 2> params = {});
144
145 /** @brief Appends a hook that fires only while condition holds. */
146 HookId add_conditional(NodeHook hook, NodeCondition condition, const std::type_info& key, uint8_t tag = 0);
147
148 /** @brief As add_conditional, returning the existing id when the same hook and condition types exist. */
149 HookId add_conditional_unique(NodeHook hook, NodeCondition condition, const std::type_info& key, uint8_t tag = 0);
150
151 /** @brief Removes the registration with this id. */
152 bool remove(HookId id);
153
154 /** @brief Removes every entry registered with this callable type. */
155 bool remove_by_key(const std::type_info& key);
156
157 /** @brief Removes the first entry registered with this callable type. */
158 bool remove_first_by_key(const std::type_info& key);
159
160 /** @brief Removes every entry whose condition has this callable type. */
161 bool remove_by_condition_key(const std::type_info& key);
162
163 /** @brief Removes every entry carrying this tag. */
164 bool remove_by_tag(uint8_t tag);
165
166 void clear();
167
168 [[nodiscard]] bool empty() const;
169 [[nodiscard]] size_t size() const;
170
171 /** @brief The current entries, held stable for the lifetime of the returned object. */
172 [[nodiscard]] Snapshot snapshot() const;
173
174 /** @brief Calls every entry whose condition, if any, holds. */
175 void fire(NodeContext& ctx) const;
176
177private:
178 struct Retired;
179
180 bool mutate(const std::function<bool(std::vector<HookEntry>&)>& edit);
181 HookId next_id() const;
182 void retire(const std::vector<HookEntry>* old);
183 void reclaim();
184
186 std::atomic<const std::vector<HookEntry>*> m_head { nullptr };
187 std::atomic<Retired*> m_retired { nullptr };
188 mutable std::atomic<uint32_t> m_readers { 0 };
189};
190
191/**
192 * @brief Wraps a context-typed hook into a NodeHook.
193 *
194 * The wrapper downcasts the context once, so callers receive their concrete context type.
195 */
196template <typename ContextT>
198{
199 return [hook](NodeContext& ctx) {
200 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
201 hook(static_cast<ContextT&>(ctx));
202 };
203}
204
205/**
206 * @brief Atomically sets a node state flag with strong memory ordering
207 * @param flag The atomic node state to modify
208 * @param expected The expected current state value
209 * @param desired The desired new state value
210 *
211 * This function safely updates a node's state, ensuring that state transitions
212 * are consistent across the audio processing graph. Node states track important
213 * conditions like whether a node is active, processed, or pending removal,
214 * which are critical for coordinating audio signal flow.
215 */
216MAYAFLUX_API void atomic_set_strong(std::atomic<NodeState>& flag, NodeState& expected, const NodeState& desired);
217
218/**
219 * @brief Atomically sets a node state flag to a specific value
220 * @param flag The atomic node state to modify
221 * @param desired The desired new state value
222 *
223 * Forcefully updates a node's state to a specific value. This is used when
224 * the node needs to be placed into a definitive state regardless of its
225 * current condition, such as when activating or deactivating nodes in the
226 * audio processing chain.
227 */
228MAYAFLUX_API void atomic_set_flag_strong(std::atomic<NodeState>& flag, const NodeState& desired);
229
230/**
231 * @brief Atomically adds a flag to a node state
232 * @param state The atomic node state to modify
233 * @param flag The flag to add to the state
234 *
235 * Adds a specific state flag to a node's state without affecting other state flags.
236 * This is used to mark nodes with specific conditions (like PROCESSED or ACTIVE)
237 * while preserving other aspects of the node's current state.
238 */
239MAYAFLUX_API void atomic_add_flag(std::atomic<NodeState>& state, NodeState flag);
240
241/**
242 * @brief Atomically removes a flag from a node state
243 * @param state The atomic node state to modify
244 * @param flags The flags to remove from the state
245 *
246 * Removes specific state flags from a node's state. This is commonly used to
247 * clear processing markers after a node has been processed, or to remove
248 * special states like PENDING_REMOVAL when they're no longer applicable.
249 */
250MAYAFLUX_API void atomic_remove_flag(std::atomic<NodeState>& state, NodeState flags);
251
252/**
253 * @brief Atomically sets a node state flag with weak memory ordering
254 * @param flag The atomic node state to modify
255 * @param expected The expected current state value
256 * @param desired The desired new state value
257 *
258 * A performance-optimized version of state setting that's used in less
259 * critical paths of the audio engine. This helps maintain node state
260 * consistency while potentially improving performance in high-throughput
261 * audio processing scenarios.
262 */
263MAYAFLUX_API void atomic_set_flag_weak(std::atomic<NodeState>& flag, NodeState& expected, const NodeState& desired);
264
265/**
266 * @brief Atomically increments the modulator count by a specified amount
267 * @param count The atomic counter to increment
268 * @param amount The amount to increment by
269 *
270 * Increases a node's modulator count, which tracks how many other nodes are
271 * currently using this node as a modulation source. This count is crucial for
272 * determining when a node's processed state can be safely reset, preventing
273 * redundant processing while ensuring all dependent nodes receive the correct
274 * modulation values.
275 */
276MAYAFLUX_API void atomic_inc_modulator_count(std::atomic<uint32_t>& count, int amount);
277
278/**
279 * @brief Atomically decrements the modulator count by a specified amount
280 * @param count The atomic counter to decrement
281 * @param amount The amount to decrement by
282 *
283 * Decreases a node's modulator count when it's no longer being used as a
284 * modulation source by another node. When the count reaches zero, the node
285 * becomes eligible for state resets, allowing the audio engine to optimize
286 * processing and avoid redundant calculations in the signal chain.
287 */
288MAYAFLUX_API void atomic_dec_modulator_count(std::atomic<uint32_t>& count, int amount);
289
290/**
291 * @brief Attempts to reset the processed state of a node
292 * @param node The node whose processed state should be reset
293 *
294 * Evaluates whether a node's processed state can be safely reset based on its
295 * current modulator count and other conditions. This is essential for the audio
296 * engine's processing cycle, as it determines which nodes need to be recalculated
297 * in the next cycle and which can reuse their previous output values, balancing
298 * processing efficiency with signal accuracy.
299 */
300MAYAFLUX_API void try_reset_processed_state(std::shared_ptr<Node> node);
301
302/**
303 * @brief Extracts active channel list from a node's channel mask
304 * @param node Node to inspect (can be null)
305 * @param fallback_channel Channel to use if node has no active channels (default: 0)
306 * @return Vector of active channel indices
307 */
308MAYAFLUX_API std::vector<uint32_t> get_active_channels(const std::shared_ptr<Nodes::Node>& node, uint32_t fallback_channel = 0);
309
310/**
311 * @brief Extracts active channel list from a channel mask
312 * @param channel_mask Bitmask of active channels
313 * @param fallback_channel Channel to use if mask is 0 (default: 0)
314 * @return Vector of active channel indices
315 */
316MAYAFLUX_API std::vector<uint32_t> get_active_channels(uint32_t channel_mask, uint32_t fallback_channel = 0);
317
318/**
319 * @brief Updates the routing state for a node based on its current channel usage
320 * @param state The routing state to update
321 *
322 * This function evaluates the current channel usage of a node and updates the
323 * routing state accordingly. It manages transitions between different routing
324 * phases (such as fade-in and fade-out(Active)) based on changes in channel counts,
325 * ensuring smooth audio output during dynamic reconfigurations of the processing graph.
326 */
327MAYAFLUX_API void update_routing_state(RoutingState& state);
328
329}
bool size
size_t count
Snapshot & operator=(Snapshot &&)=delete
const HookEntry * end() const
Snapshot(Snapshot &&other) noexcept
Snapshot(const HookList *list, const std::vector< HookEntry > *entries)
Snapshot(const Snapshot &)=delete
const HookEntry * begin() const
Snapshot & operator=(const Snapshot &)=delete
The entries of a list at one instant, kept alive while this object exists.
Definition NodeUtils.hpp:99
HookList & operator=(HookList &&)=delete
HookList(const HookList &)=delete
HookList & operator=(const HookList &)=delete
HookList(HookList &&)=delete
Stores, fires and clears the hooks of one node channel.
Definition NodeUtils.hpp:93
Base context class for node callbacks.
Definition Node.hpp:53
Base interface for all computational processing nodes.
Definition Node.hpp:126
TypedHook<> NodeHook
Alias for TypedHook<NodeContext>.
Definition NodeUtils.hpp:38
NodeState
Represents the processing state of a node in the audio graph.
Definition NodeSpec.hpp:43
void try_reset_processed_state(std::shared_ptr< Node > node)
Attempts to reset the processed state of a node.
void atomic_set_flag_weak(std::atomic< NodeState > &flag, NodeState &expected, const NodeState &desired)
Atomically sets a node state flag with weak memory ordering.
void atomic_inc_modulator_count(std::atomic< uint32_t > &count, int amount)
Atomically increments the modulator count by a specified amount.
uint64_t HookId
Identifies one registration inside a HookList.
Definition NodeUtils.hpp:60
void atomic_dec_modulator_count(std::atomic< uint32_t > &count, int amount)
Atomically decrements the modulator count by a specified amount.
void update_routing_state(RoutingState &state)
Updates the routing state for a node based on its current channel usage.
void atomic_set_strong(std::atomic< NodeState > &flag, NodeState &expected, const NodeState &desired)
Atomically sets a node state flag with strong memory ordering.
std::function< void(ContextT &)> TypedHook
Callback function type for node processing events, parameterised on context type.
Definition NodeUtils.hpp:28
void atomic_remove_flag(std::atomic< NodeState > &state, NodeState flag)
Atomically removes a flag from a node state.
std::function< bool(NodeContext &)> NodeCondition
Predicate function type for conditional callbacks.
Definition NodeUtils.hpp:54
void atomic_set_flag_strong(std::atomic< NodeState > &flag, const NodeState &desired)
Atomically sets a node state flag to a specific value.
std::vector< uint32_t > get_active_channels(const std::shared_ptr< Nodes::Node > &node, uint32_t fallback_channel)
Extracts active channel list from a node's channel mask.
NodeHook adapt_hook(const TypedHook< ContextT > &hook)
Wraps a context-typed hook into a NodeHook.
void atomic_add_flag(std::atomic< NodeState > &state, NodeState flag)
Atomically adds a flag to a node state.
Contains the node-based computational processing system components.
Definition Chronie.hpp:14
std::array< double, 2 > params
Definition NodeUtils.hpp:74
const std::type_info * key
Definition NodeUtils.hpp:72
std::optional< NodeCondition > condition
Definition NodeUtils.hpp:76
One registration held by a HookList.
Definition NodeUtils.hpp:70