MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
NodeGraphManager.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "RootNode.hpp"
4
5namespace MayaFlux::Nodes {
6
7using TokenChannelProcessor = std::function<std::vector<double>(RootNode*, uint32_t)>;
8using TokenSampleProcessor = std::function<double(RootNode*, uint32_t)>;
9
10namespace Network {
11 class NodeNetwork;
12} // namespace Network
13
14/**
15 * @class NodeGraphManager
16 * @brief Central manager for the computational processing node graph
17 *
18 * The NodeGraphManager is the primary interface for creating, connecting, and managing
19 * processing nodes in the MayaFlux engine. It serves as a registry for all nodes
20 * and maintains the root nodes for each processing channel and processing domain (token).
21 *
22 * Features:
23 * - Multi-modal (token-based) processing: Supports multiple independent processing domains
24 * (e.g., AUDIO_RATE, VISUAL_RATE, CUSTOM_RATE), each with its own set of channels and root nodes.
25 * - Per-channel root nodes: Each processing domain can have multiple channels, each with its own RootNode.
26 * - Node registry: Nodes are registered by string identifier for easy lookup and connection.
27 * - Flexible connection: Nodes can be connected by reference or by identifier, supporting both direct and named graph construction.
28 * - Subsystem token processors: Allows registration of custom processing functions for each token, enabling efficient backend-specific processing.
29 *
30 * This class provides methods to:
31 * - Create nodes of any type with automatic registration
32 * - Connect nodes to form processing chains
33 * - Add nodes to channel root nodes for output
34 * - Look up nodes by their string identifiers
35 *
36 * The NodeGraphManager maintains separate root nodes for each processing channel.
37 * Channels are local to a specific processing domain.
38 * This allows for independent transformation chains per channel while sharing nodes
39 * between channels when needed.
40 */
41class MAYAFLUX_API NodeGraphManager {
42public:
43 /**
44 * @brief Creates a new NodeGraphManager
45 *
46 * Initializes the manager with a root node for channel 0 (the default channel)
47 * in the AUDIO_RATE domain. Additional root nodes for other tokens and channels
48 * are created on demand when accessed.
49 */
51
52 /**
53 * @brief Destroys the NodeGraphManager
54 *
55 * Cleans up all registered nodes and networks.
56 */
58
59 // NodeGraphManager is non-copyable and non-moveable due to internal atomic state
64
65 /**
66 * @brief Add node to specific processing token and channel
67 * @param node Node to add
68 * @param token Processing domain (AUDIO_RATE, VISUAL_RATE, etc.)
69 * @param channel Channel within that domain
70 *
71 * Registers the node with the specified processing domain and channel.
72 * The node's output will contribute to that token/channel's output.
73 * If the node is not already globally registered, it will be registered automatically.
74 */
75 void add_to_root(const std::shared_ptr<Node>& node, ProcessingToken token, unsigned int channel = 0);
76
77 /**
78 * @brief Remove node from a specific processing token and channel
79 * @param node Node to remove
80 * @param token Processing domain (AUDIO_RATE, VISUAL_RATE, etc.)
81 * @param channel Channel within that domain
82 *
83 * Removes the specified node from the root node of the given processing domain and channel.
84 * If the node is not found in that root, no action is taken.
85 */
86 void remove_from_root(const std::shared_ptr<Node>& node, ProcessingToken token, unsigned int channel = 0);
87
88 /**
89 * @brief Adds a node to a channel's root node by its identifier
90 * @param node_id Identifier of the node to add
91 @ param token Processing domain to add the node to (default is AUDIO_RATE)
92 * @param channel Channel index to add the node to (AUDIO_RATE domain)
93 *
94 * Looks up the node by its identifier and adds it to the specified
95 * channel's root node in the specified processing domain (default is AUDIO_RATE).
96 * Throws an exception if the node identifier is not found.
97 */
98 inline void add_to_root(const std::string& node_id, ProcessingToken token = ProcessingToken::AUDIO_RATE, unsigned int channel = 0)
99 {
100 auto node = get_node(node_id);
101 if (node) {
102 add_to_root(node, token, channel);
103 }
104 }
105
106 /**
107 * @brief Register subsystem processor for a specific token
108 * @param token Processing domain to handle (e.g., AUDIO_RATE, VISUAL_RATE)
109 * @param processor Function that receives a span of root nodes for that token
110 *
111 * Registers a custom processing function for a given processing domain (token).
112 * When process_token() is called for that token, the registered processor will
113 * be invoked with a span of all root nodes for that domain, enabling efficient
114 * backend-specific or multi-channel processing.
115 */
116 void register_token_processor(ProcessingToken token,
117 std::function<void(std::span<RootNode*>)> processor);
118
119 /**
120 * @brief Gets all channel root nodes for the AUDIO_RATE domain
121 * @param token Processing domain to get the root nodes for (default is AUDIO_RATE)
122 * @return Constant reference to the map of channel indices to root nodes
123 *
124 * This provides access to all channel root nodes of the specified domain that have been created.
125 * Useful for processing all channels or inspecting the node graph structure.
126 * For multi-modal access, use get_token_roots().
127 */
128 const std::unordered_map<unsigned int, std::shared_ptr<RootNode>>& get_all_channel_root_nodes(ProcessingToken token = ProcessingToken::AUDIO_RATE) const;
129
130 /**
131 * @brief Creates and registers a new node of the specified type
132 * @tparam NodeType The type of node to create (must derive from Node)
133 * @tparam Args Parameter types for the node's constructor
134 * @param id String identifier for the node
135 * @param args Constructor arguments for the node
136 * @return Shared pointer to the created node
137 *
138 * This template method creates a node of any type that derives from Node,
139 * passing the provided arguments to its constructor. The created node is
140 * automatically registered with the given string identifier for later lookup.
141 *
142 * Example:
143 * ```cpp
144 * auto sine = manager.create_node<Generators::Sine>("my_sine", 440.0, 0.5);
145 * auto filter = manager.create_node<Filters::LowPass>("my_filter", sine, 1000.0);
146 * ```
147 */
148 template <typename NodeType, typename... Args>
149 inline std::shared_ptr<NodeType> create_node(const std::string& id, Args&&... args)
150 {
151 auto node = std::make_shared<NodeType>(std::forward<Args>(args)...);
152 m_Node_registry[id] = node;
153 return node;
154 }
155
156 /**
157 * @brief Looks up a node by its string identifier
158 * @param id The string identifier of the node
159 * @return Shared pointer to the node, or nullptr if not found
160 *
161 * Retrieves a previously registered node using its string identifier.
162 * This allows for connecting nodes by name rather than requiring direct
163 * references to node objects.
164 */
165 std::shared_ptr<Node> get_node(const std::string& id);
166
167 /**
168 * @brief Connects two nodes by their string identifiers
169 * @param source_id Identifier of the source node
170 * @param target_id Identifier of the target node
171 *
172 * Looks up both nodes by their identifiers and establishes a connection
173 * where the output of the source node becomes an input to the target node.
174 *
175 * Equivalent to:
176 * ```cpp
177 * connect(get_node(source_id), get_node(target_id));
178 * ```
179 *
180 * Throws an exception if either node identifier is not found.
181 */
182 void connect(const std::string& source_id, const std::string& target_id);
183
184 /**
185 * @brief Checks if a node is registered with this manager
186 * @param node Node to check
187 * @return true if the node is registered, false otherwise
188 *
189 * A node is considered registered if it exists in the node registry
190 * with any identifier.
191 */
192 bool is_node_registered(const std::shared_ptr<Node>& node);
193
194 /**
195 * @brief Process all nodes in a specific token domain
196 * Calls registered processor if available, otherwise calls process() on each root
197 * @param token Processing domain to process
198 * @param num_samples Number of samples/frames to process
199 *
200 * Processes all root nodes for the specified processing domain (token).
201 * If a custom processor is registered for the token, it is called with all root nodes.
202 * Otherwise, process() is called on each root node individually.
203 */
204 void process_token(ProcessingToken token, unsigned int num_samples = 1);
205
206 /**
207 * @brief Register per-channel processor for a specific token
208 * @param token Processing domain to handle (e.g., AUDIO_RATE, VISUAL_RATE)
209 * @param processor Function that receives a single root node and returns processed data
210 *
211 * Registers a per-channel processing function that processes one root node at a time
212 * and returns the processed data. This enables coordination with buffer management
213 * on a per-channel basis.
214 */
215 void register_token_channel_processor(ProcessingToken token,
216 TokenChannelProcessor processor);
217
218 /**
219 * @brief Register per-sample processor for a specific token
220 * @param token Processing domain to handle (e.g., AUDIO_RATE, VISUAL_RATE)
221 * @param processor Function that processes a single sample and returns the processed value
222 *
223 * Registers a per-sample processing function that processes one sample at a time
224 * and returns the processed value. This is useful for low-level sample manipulation.
225 */
226 void register_token_sample_processor(ProcessingToken token,
227 TokenSampleProcessor processor);
228
229 /**
230 * @brief Process a specific channel within a token domain
231 * @param token Processing domain
232 * @param channel Channel index within that domain
233 * @param num_samples Number of samples/frames to process
234 * @return Processed data from the channel's root node
235 *
236 * Processes a single channel's root node and returns the processed data.
237 * If a custom per-channel processor is registered, it is used; otherwise,
238 * the default root node processing is performed.
239 */
240 std::vector<double> process_channel(ProcessingToken token, unsigned int channel, unsigned int num_samples);
241
242 /**
243 * @brief Process a single sample for a specific channel
244 * @param token Processing domain
245 * @param channel Channel index within that domain
246 * @return Processed sample value from the channel's root node
247 *
248 * Processes a single sample for the specified channel and returns the processed value.
249 * If a custom per-sample processor is registered, it is used; otherwise, the default
250 * root node processing is performed.
251 * As node graph manager feeds into hardware audio output, the value returned is normalized
252 */
253 double process_sample(ProcessingToken token, uint32_t channel);
254
255 /**
256 * @brief Process all channels for a token and return channel-separated data
257 * @param token Processing domain
258 * @param num_samples Number of samples/frames to process
259 * @return Map of channel index to processed data
260 *
261 * Processes all channels for a token and returns a map where each channel
262 * index maps to its processed data. This enables bulk processing while
263 * maintaining per-channel data separation.
264 */
265 std::unordered_map<unsigned int, std::vector<double>> process_token_with_channel_data(
266 ProcessingToken token, unsigned int num_samples);
267
268 /**
269 * @brief Get the number of active channels for a specific token
270 * @param token Processing domain
271 * @return Number of channels that have active root nodes
272 */
273 unsigned int get_channel_count(ProcessingToken token) const;
274
275 /**
276 * @brief Get spans of root nodes for a token (for custom processing)
277 * @param token Processing domain
278 * @return Vector of RootNode pointers for that domain
279 *
280 * Returns a vector of pointers to all root nodes for the specified processing domain.
281 * Useful for custom processing, introspection, or multi-channel operations.
282 */
283 std::vector<RootNode*> get_all_root_nodes(ProcessingToken token);
284
285 /**
286 * @brief Gets or creates the root node for a specific token and channel
287 * @param token Processing domain
288 * @param channel Channel index
289 * @return Reference to the root node for the given token and channel
290 *
291 * If the root node does not exist, it is created and registered.
292 */
293 RootNode& get_root_node(ProcessingToken token, unsigned int channel);
294
295 /**
296 * @brief Process all active tokens sequentially
297 * @param num_samples Number of samples/frames to process
298 *
299 * Iterates over all processing domains (tokens) that have active root nodes,
300 * and processes each one in turn. This enables multi-modal, multi-channel
301 * processing in a single call.
302 */
303 void process_all_tokens(unsigned int num_samples = 1);
304
305 /**
306 * @brief Gets all currently active processing tokens (domains)
307 * @return Vector of active ProcessingToken values
308 *
309 * Returns a list of all processing domains that have at least one root node.
310 * Useful for introspection and for iterating over all active domains.
311 */
312 std::vector<ProcessingToken> get_active_tokens() const;
313
314 /**
315 * @brief Gets all channel indices for a given processing token
316 * @param token Processing domain
317 * @return Vector of channel indices that have root nodes for this token
318 */
319 std::vector<unsigned int> get_all_channels(ProcessingToken token) const;
320
321 /**
322 * @brief Gets the total number of nodes registered under a given token
323 * @param token Processing domain
324 * @return Total number of nodes across all channels for this token
325 */
326 size_t get_node_count(ProcessingToken token) const;
327
328 /**
329 * @brief Prints a summary of all tokens, channels, and node counts
330 *
331 * Outputs a human-readable summary of the current node graph structure,
332 * including the number of tokens, channels, and nodes per domain.
333 * Useful for debugging and introspection.
334 */
335 void print_summary() const;
336
337 //-------------------------------------------------------------------------
338 // NodeNetwork Management
339 //-------------------------------------------------------------------------
340
341 /**
342 * @brief Add a network to a processing token
343 * @param network Network to add
344 * @param token Processing domain (AUDIO_RATE, VISUAL_RATE, etc.)
345 *
346 * Networks are processed parallel to RootNodes, managing their own
347 * internal node coordination and processing.
348 */
349 void add_network(const std::shared_ptr<Network::NodeNetwork>& network, ProcessingToken token);
350
351 /**
352 * @brief Remove a network from a processing token
353 * @param network Network to remove
354 * @param token Processing domain
355 * @param channel Channel index within that domain, optional
356 */
357 void remove_network(const std::shared_ptr<Network::NodeNetwork>& network, ProcessingToken token);
358
359 /**
360 * @brief Get all networks for a specific token
361 * @param token Processing domain
362 * @return Vector of networks registered to this token
363 */
364 [[nodiscard]] std::vector<std::shared_ptr<Network::NodeNetwork>> get_networks(ProcessingToken token, uint32_t channel = 0) const;
365
366 /**
367 * @brief Get all networks for a specific token across all channels
368 * @param token Processing domain
369 * @return Vector of networks registered to this token
370 */
371 [[nodiscard]] std::vector<std::shared_ptr<Network::NodeNetwork>> get_all_networks(ProcessingToken token) const;
372
373 /**
374 * @brief Get count of networks for a token
375 */
376 [[nodiscard]] size_t get_network_count(ProcessingToken token) const;
377
378 /**
379 * @brief Clear all networks from a token
380 */
381 void clear_networks(ProcessingToken token);
382
383 /**
384 * @brief Register network globally (like nodes)
385 */
386 void register_network_global(const std::shared_ptr<Network::NodeNetwork>& network);
387
388 /**
389 * @brief Unregister network globally
390 */
391 void unregister_network_global(const std::shared_ptr<Network::NodeNetwork>& network);
392
393 /**
394 * @brief Process audio networks for a specific channel
395 * @param token Processing domain (should be AUDIO_RATE)
396 * @param num_samples Number of samples/frames to process
397 * @param channel Channel index within that domain
398 * @return Vector of processed audio data from all networks for that channel
399 *
400 * Processes all audio-sink networks registered to the specified channel
401 * and returns their combined output data.
402 */
403 std::vector<std::vector<double>> process_audio_networks(ProcessingToken token, uint32_t num_samples, uint32_t channel = 0);
404
405 /**
406 * @brief Terminates all active processing across all tokens and channels
407 *
408 * This method stops all active processing contexts in all root nodes
409 * and networks, ensuring a clean shutdown of processing activities.
410 */
411 void terminate_active_processing();
412
413private:
414 /**
415 * @brief Map of channel indices to their root nodes (AUDIO_RATE domain)
416 *
417 * Each audio channel has its own root node that collects all nodes
418 * that should output to that channel. For multi-modal support,
419 * see m_token_roots.
420 */
421 std::unordered_map<unsigned int, std::shared_ptr<RootNode>> m_channel_root_nodes;
422
423 /**
424 * @brief Registry of all nodes by their string identifiers
425 *
426 * This registry allows nodes to be looked up by their string identifiers
427 * for operations like connecting nodes by name.
428 */
429 std::unordered_map<std::string, std::shared_ptr<Node>> m_Node_registry;
430
431 /**
432 * @brief Multi-modal map of processing tokens to their channel root nodes
433 *
434 * Each processing domain (token) can have multiple channels, each with its own RootNode.
435 * Enables support for audio, visual, and custom processing domains.
436 */
437 std::unordered_map<ProcessingToken,
438 std::unordered_map<unsigned int, std::shared_ptr<RootNode>>>
440
441 /**
442 * @brief Registered custom processors for each processing token
443 *
444 * Maps each processing domain (token) to a custom processing function that
445 * receives a span of all root nodes for that domain. Enables efficient
446 * backend-specific or multi-channel processing.
447 */
448 std::unordered_map<ProcessingToken,
449 std::function<void(std::span<RootNode*>)>>
451
452 /**
453 * @brief Per-channel processors for each processing token
454 *
455 * Maps each processing domain to a per-channel processing function that
456 * processes a single root node and returns processed data. This enables
457 * fine-grained processing with data extraction capabilities.
458 */
459 std::unordered_map<ProcessingToken, TokenChannelProcessor> m_token_channel_processors;
460
461 /**
462 * @brief Per-sample processors for each processing token
463 *
464 * Maps each processing domain to a per-sample processing function that
465 * processes a single sample and returns the processed value. This is useful
466 * for low-level sample manipulation and custom processing.
467 */
468 std::unordered_map<ProcessingToken, TokenSampleProcessor> m_token_sample_processors;
469
470 /**
471 * @brief Global network registry (like m_Node_registry)
472 *
473 * Maps generated IDs to networks for lifecycle management
474 */
475 std::unordered_map<std::string, std::shared_ptr<Network::NodeNetwork>> m_network_registry;
476
477 /**
478 * @brief Audio-sink networks
479 * Only populated for networks with OutputMode::AUDIO_SINK
480 */
481 std::unordered_map<ProcessingToken,
482 std::vector<std::shared_ptr<Network::NodeNetwork>>>
484
485 /**
486 * @brief Non-audio networks (token-level processing)
487 * For NONE, GRAPHICS_BIND, CUSTOM output modes
488 */
489 std::unordered_map<ProcessingToken, std::vector<std::shared_ptr<Network::NodeNetwork>>>
491
492 /**
493 * @brief Processing flags for each token's networks
494 *
495 * Used to prevent re-entrant processing of networks within the same cycle.
496 */
497 std::unordered_map<ProcessingToken, std::unique_ptr<std::atomic<bool>>> m_token_network_processing;
498
499 std::atomic<bool> m_terminate_requested { false }; ///< Global termination flag
500
501 /**
502 * @brief Ensures a root node exists for the given token and channel
503 * @param token Processing domain
504 * @param channel Channel index
505 *
506 * Creates and registers a new root node if one does not already exist.
507 */
508 void ensure_root_exists(ProcessingToken token, unsigned int channel);
509
510 /**
511 * @brief Ensures that a processing token entry exists
512 * @param token Processing domain
513 * @param num_channels Number of channels to initialize (default: 1)
514 *
515 * Creates the necessary data structures for the given processing token
516 * if they do not already exist.
517 */
518 void ensure_token_exists(ProcessingToken token, uint32_t num_channels = 1);
519
520 /**
521 * @brief Registers a node globally if not already registered
522 * @param node Node to register
523 *
524 * Assigns a generated identifier if needed and adds the node to the registry.
525 */
526 void register_global(const std::shared_ptr<Node>& node);
527
528 /**
529 * @brief Adds the specified channel mask to a node's global registration
530 * @param node Node to modify
531 * @param channel_id Channel mask to set
532 */
533 void set_channel_mask(const std::shared_ptr<Node>& node, uint32_t channel_id);
534
535 /**
536 * @brief Unsets the specified channel mask from a node's global registration
537 * @param node Node to modify
538 * @param channel_id Channel mask to unset
539 *
540 * Removes the specified channel mask from the node's global registration.
541 */
542 void unset_channel_mask(const std::shared_ptr<Node>& node, uint32_t channel_id);
543
544 /**
545 * @brief Unregisters a node globally
546 * @param node Node to unregister
547 *
548 * Removes the node from the global registry and cleans up any references.
549 */
550 void unregister_global(const std::shared_ptr<Node>& node);
551
552 /**
553 * @brief Normalizes a sample to the range [-1, 1] based on the number of nodes
554 * @param sample Reference to the sample value to normalize
555 * @param num_nodes Number of nodes in the processing chain
556 *
557 * Ensures that the sample value is within the valid range for audio processing.
558 */
559 void normalize_sample(double& sample, uint32_t num_nodes);
560
561 /**
562 * @brief Check if network is registered globally
563 */
564 bool is_network_registered(const std::shared_ptr<Network::NodeNetwork>& network);
565
566 /**
567 * @brief Resets the processing state of audio networks for a token and channel
568 * @param token Processing domain
569 * @param channel Channel index
570 */
571 void reset_audio_network_state(ProcessingToken token, uint32_t channel = 0);
572
573 /**
574 * @brief Preprocess networks for a specific token
575 * @param token Processing domain
576 * @return true if preprocessing succeeded, false otherwise
577 */
578 bool preprocess_networks(ProcessingToken token);
579
580 /**
581 * @brief Postprocess networks for a specific token and channel
582 * @param token Processing domain
583 * @param channel Channel index
584 */
585 void postprocess_networks(ProcessingToken token, std::optional<uint32_t> channel);
586};
587
588}
static MayaFlux::Nodes::ProcessingToken token
Definition Timers.cpp:8
NodeGraphManager(NodeGraphManager &&)=delete
std::unordered_map< unsigned int, std::shared_ptr< RootNode > > m_channel_root_nodes
Map of channel indices to their root nodes (AUDIO_RATE domain)
std::unordered_map< ProcessingToken, std::vector< std::shared_ptr< Network::NodeNetwork > > > m_token_networks
Non-audio networks (token-level processing) For NONE, GRAPHICS_BIND, CUSTOM output modes.
void add_to_root(const std::string &node_id, ProcessingToken token=ProcessingToken::AUDIO_RATE, unsigned int channel=0)
Adds a node to a channel's root node by its identifier.
std::unordered_map< ProcessingToken, std::function< void(std::span< RootNode * >)> > m_token_processors
Registered custom processors for each processing token.
std::unordered_map< std::string, std::shared_ptr< Network::NodeNetwork > > m_network_registry
Global network registry (like m_Node_registry)
NodeGraphManager & operator=(const NodeGraphManager &)=delete
std::shared_ptr< NodeType > create_node(const std::string &id, Args &&... args)
Creates and registers a new node of the specified type.
std::unordered_map< ProcessingToken, TokenSampleProcessor > m_token_sample_processors
Per-sample processors for each processing token.
std::unordered_map< std::string, std::shared_ptr< Node > > m_Node_registry
Registry of all nodes by their string identifiers.
NodeGraphManager(const NodeGraphManager &)=delete
std::unordered_map< ProcessingToken, std::unordered_map< unsigned int, std::shared_ptr< RootNode > > > m_token_roots
Multi-modal map of processing tokens to their channel root nodes.
std::unordered_map< ProcessingToken, std::vector< std::shared_ptr< Network::NodeNetwork > > > m_audio_networks
Audio-sink networks Only populated for networks with OutputMode::AUDIO_SINK.
NodeGraphManager & operator=(NodeGraphManager &&)=delete
std::unordered_map< ProcessingToken, TokenChannelProcessor > m_token_channel_processors
Per-channel processors for each processing token.
std::unordered_map< ProcessingToken, std::unique_ptr< std::atomic< bool > > > m_token_network_processing
Processing flags for each token's networks.
Central manager for the computational processing node graph.
Container for top-level nodes in a processing channel with multi-modal support.
Definition RootNode.hpp:29
ProcessingToken
Enumerates the different processing domains for nodes.
std::function< double(RootNode *, uint32_t)> TokenSampleProcessor
std::function< std::vector< double >(RootNode *, uint32_t)> TokenChannelProcessor
Contains the node-based computational processing system components.
Definition Chronie.hpp:5