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