@@ -76,8 +76,10 @@ pub enum GraphRuntimeRequest {
7676#[ derive( Debug , serde:: Serialize , serde:: Deserialize ) ]
7777pub struct GraphUpdate {
7878 pub ( super ) network : NodeNetwork ,
79- /// The node that should be temporary inspected during execution
80- pub ( super ) node_to_inspect : Option < NodeId > ,
79+ /// Full path from the root network to the node that should be temporarily inspected during execution.
80+ /// The last element is the inspect target; preceding elements identify the nested subnetwork it lives in,
81+ /// so the runtime can splice its monitor node alongside the target instead of only at the top level.
82+ pub ( super ) node_to_inspect : Vec < NodeId > ,
8183}
8284
8385#[ derive( Default , Debug , Clone , serde:: Serialize , serde:: Deserialize ) ]
@@ -235,7 +237,7 @@ impl NodeRuntime {
235237 }
236238 GraphRuntimeRequest :: GraphUpdate ( GraphUpdate { mut network, node_to_inspect } ) => {
237239 // Insert the monitor node to manage the inspection
238- self . inspect_state = node_to_inspect . map ( |inspect| InspectState :: monitor_inspect_node ( & mut network, inspect ) ) ;
240+ self . inspect_state = InspectState :: monitor_inspect_node ( & mut network, & node_to_inspect ) ;
239241
240242 self . old_graph = Some ( network. clone ( ) ) ;
241243
@@ -264,7 +266,7 @@ impl NodeRuntime {
264266 self . update_thumbnails = false ;
265267
266268 // Resolve the result from the inspection by accessing the monitor node
267- let inspect_result = self . inspect_state . and_then ( |state| state. access ( & self . executor ) ) ;
269+ let inspect_result = self . inspect_state . as_ref ( ) . and_then ( |state| state. access ( & self . executor ) ) ;
268270
269271 let ( result, texture) = match result {
270272 Ok ( TaggedValue :: RenderOutput ( RenderOutput {
@@ -408,7 +410,11 @@ impl NodeRuntime {
408410
409411 for monitor_node_path in & self . monitor_nodes {
410412 // Skip the inspect monitor node
411- if self . inspect_state . is_some_and ( |inspect_state| monitor_node_path. last ( ) . copied ( ) == Some ( inspect_state. monitor_node ) ) {
413+ if self
414+ . inspect_state
415+ . as_ref ( )
416+ . is_some_and ( |inspect_state| monitor_node_path. last ( ) . copied ( ) == Some ( inspect_state. monitor_node ) )
417+ {
412418 continue ;
413419 }
414420
@@ -540,16 +546,22 @@ pub async fn replace_application_io(application_io: PlatformApplicationIo) {
540546}
541547
542548/// Which node is inspected and which monitor node is used (if any) for the current execution
543- #[ derive( Debug , Clone , Copy ) ]
549+ #[ derive( Debug , Clone ) ]
544550struct InspectState {
545551 inspect_node : NodeId ,
546552 monitor_node : NodeId ,
553+ /// Path of the subnetwork the monitor was inserted into (i.e., the parent of `inspect_node`).
554+ /// Used to construct the full node path when introspecting the monitor's value.
555+ monitor_parent_path : Vec < NodeId > ,
547556}
548557/// The resulting value from the temporary inspected during execution
549558#[ derive( Clone , Debug , Default ) ]
550559pub struct InspectResult {
551560 introspected_data : Option < Arc < dyn std:: any:: Any + Send + Sync + ' static > > ,
552- pub inspect_node : NodeId ,
561+ /// Full path from the root network to the inspected node, with the node itself as the last element.
562+ /// The parent slice (`split_last().1`) is the network the node lives in, which downstream consumers
563+ /// (e.g. the Data panel) need when looking the node up via `network_interface.is_layer(...)` etc.
564+ pub inspect_node_path : Vec < NodeId > ,
553565}
554566
555567impl InspectResult {
@@ -561,17 +573,21 @@ impl InspectResult {
561573// This is very ugly but is required to be inside a message
562574impl PartialEq for InspectResult {
563575 fn eq ( & self , other : & Self ) -> bool {
564- self . inspect_node == other. inspect_node
576+ self . inspect_node_path == other. inspect_node_path
565577 }
566578}
567579
568580impl InspectState {
569- /// Insert the monitor node to manage the inspection
570- pub fn monitor_inspect_node ( network : & mut NodeNetwork , inspect_node : NodeId ) -> Self {
581+ /// Insert the monitor node alongside the inspect node identified by `inspect_path` (full path from root, last element is the target).
582+ /// Returns `None` if the path is empty or doesn't resolve to a node inside a reachable subnetwork.
583+ pub fn monitor_inspect_node ( network : & mut NodeNetwork , inspect_path : & [ NodeId ] ) -> Option < Self > {
584+ let ( inspect_node, parent_path) = inspect_path. split_last ( ) ?;
585+ let inspect_node = * inspect_node;
586+ let target_network = navigate_to_network_mut ( network, parent_path) ?;
571587 let monitor_id = NodeId :: new ( ) ;
572588
573589 // It is necessary to replace the inputs before inserting the monitor node to avoid changing the input of the new monitor node
574- for input in network . nodes . values_mut ( ) . flat_map ( |node| node. inputs . iter_mut ( ) ) . chain ( & mut network . exports ) {
590+ for input in target_network . nodes . values_mut ( ) . flat_map ( |node| node. inputs . iter_mut ( ) ) . chain ( & mut target_network . exports ) {
575591 let NodeInput :: Node { node_id, output_index, .. } = input else { continue } ;
576592 // We only care about the primary output of our inspect node
577593 if * output_index != 0 || * node_id != inspect_node {
@@ -588,21 +604,39 @@ impl InspectState {
588604 skip_deduplication : true ,
589605 ..Default :: default ( )
590606 } ;
591- network . nodes . insert ( monitor_id, monitor_node) ;
607+ target_network . nodes . insert ( monitor_id, monitor_node) ;
592608
593- Self {
609+ Some ( Self {
594610 inspect_node,
595611 monitor_node : monitor_id,
596- }
612+ monitor_parent_path : parent_path. to_vec ( ) ,
613+ } )
597614 }
598615 /// Resolve the result from the inspection by accessing the monitor node
599616 fn access ( & self , executor : & DynamicExecutor ) -> Option < InspectResult > {
600- let introspected_data = executor. introspect ( & [ self . monitor_node ] ) . inspect_err ( |e| warn ! ( "Failed to introspect monitor node {e}" ) ) . ok ( ) ;
617+ // The executor's source map indexes by full path from root, so prepend the subnetwork path to the monitor ID.
618+ let mut monitor_path = self . monitor_parent_path . clone ( ) ;
619+ monitor_path. push ( self . monitor_node ) ;
620+ let introspected_data = executor. introspect ( & monitor_path) . inspect_err ( |e| warn ! ( "Failed to introspect monitor node {e}" ) ) . ok ( ) ;
601621 // TODO: Consider displaying the error instead of ignoring it
602622
603- Some ( InspectResult {
604- inspect_node : self . inspect_node ,
605- introspected_data,
606- } )
623+ let mut inspect_node_path = self . monitor_parent_path . clone ( ) ;
624+ inspect_node_path. push ( self . inspect_node ) ;
625+ Some ( InspectResult { inspect_node_path, introspected_data } )
626+ }
627+ }
628+
629+ /// Walks `network` down through `path`, returning a mutable reference to the nested `NodeNetwork`
630+ /// at the end. Each path element must name a `DocumentNode` whose implementation is `Network(...)`.
631+ /// Returns `None` if any step is missing or doesn't refer to a subnetwork.
632+ fn navigate_to_network_mut < ' a > ( network : & ' a mut NodeNetwork , path : & [ NodeId ] ) -> Option < & ' a mut NodeNetwork > {
633+ let mut current = network;
634+ for node_id in path {
635+ let node = current. nodes . get_mut ( node_id) ?;
636+ current = match & mut node. implementation {
637+ DocumentNodeImplementation :: Network ( nested) => nested,
638+ _ => return None ,
639+ } ;
607640 }
641+ Some ( current)
608642}
0 commit comments