1use std::cell::RefCell;
24use std::rc::{Rc, Weak};
25
26use graphrefly_core::{
27 Core, EqualsMode, FnId, HandleId, LockId, NodeId, PauseError, ResumeReport, SetDepsError, Sink,
28 SubscriptionId, TopologySubscriptionId,
29};
30use indexmap::IndexMap;
31
32use crate::debug::DebugBindingBoundary;
33use crate::describe::{
34 describe_of, describe_reactive_in, DescribeSink, GraphDescribeOutput, ReactiveDescribeHandle,
35};
36use crate::mount::{GraphRemoveAudit, MountError};
37use crate::observe::{GraphObserveAll, GraphObserveAllReactive, GraphObserveOne};
38
39pub(crate) const PATH_SEP: &str = "::";
41
42#[derive(Debug, thiserror::Error)]
44pub enum RemoveError {
45 #[error("Graph::remove: name `{0}` not found (neither a node nor a mounted subgraph)")]
46 NotFound(String),
47 #[error("Graph::remove: graph has been destroyed")]
48 Destroyed,
49}
50
51#[derive(Debug, Clone, Copy)]
53pub enum SignalKind {
54 Invalidate,
56 Pause(LockId),
58 Resume(LockId),
60 Complete,
62 Error(HandleId),
64}
65
66#[derive(Debug, thiserror::Error)]
68pub enum PathError {
69 #[error("Path is empty")]
71 Empty,
72 #[error("Path segment `..` used on root graph (no parent)")]
74 NoParent,
75 #[error("Path segment `{0}` does not match any child graph")]
77 ChildNotFound(String),
78 #[error("Graph has been destroyed")]
80 Destroyed,
81}
82
83#[derive(Debug, thiserror::Error)]
85pub enum NameError {
86 #[error("Graph::add: name `{0}` already registered in this graph")]
87 Collision(String),
88 #[error("Graph: name `{0}` may not contain the `::` path separator")]
89 InvalidName(String),
90 #[error("Graph: name `{0}` uses the reserved `_anon_` prefix (collides with anonymous node describe format)")]
91 ReservedPrefix(String),
92 #[error("Graph: graph has been destroyed; further registration refused")]
93 Destroyed,
94}
95
96pub(crate) type NamespaceChangeSink = Rc<dyn Fn(&Core)>;
106
107static_assertions::assert_not_impl_any!(NamespaceChangeSink: Send, Sync);
108
109pub struct GraphInner {
116 pub(crate) name: String,
117 pub(crate) names: IndexMap<String, NodeId>,
120 pub(crate) names_inverse: IndexMap<NodeId, String>,
122 pub(crate) children: IndexMap<String, Rc<RefCell<GraphInner>>>,
126 pub(crate) parent: Option<Weak<RefCell<GraphInner>>>,
129 pub(crate) destroyed: bool,
131 pub(crate) namespace_sinks: IndexMap<u64, NamespaceChangeSink>,
134 pub(crate) next_ns_sink_id: u64,
135 pub(crate) factory: Option<String>,
140 pub(crate) factory_args: Option<serde_json::Value>,
147}
148
149#[derive(Clone)]
159pub struct Graph {
160 pub(crate) inner: Rc<RefCell<GraphInner>>,
161}
162
163impl std::fmt::Debug for Graph {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 let inner = self.inner.borrow_mut();
166 f.debug_struct("Graph")
167 .field("name", &inner.name)
168 .field("node_count", &inner.names.len())
169 .field("subgraph_count", &inner.children.len())
170 .field("destroyed", &inner.destroyed)
171 .finish_non_exhaustive()
172 }
173}
174
175#[allow(clippy::missing_panics_doc, clippy::must_use_candidate)]
179impl Graph {
180 #[must_use]
185 pub fn new(name: impl Into<String>) -> Self {
186 Self::with_parent(name.into(), None)
187 }
188
189 pub(crate) fn with_parent(name: String, parent: Option<Weak<RefCell<GraphInner>>>) -> Self {
190 Self {
191 inner: Rc::new(RefCell::new(GraphInner {
192 name,
193 names: IndexMap::new(),
194 names_inverse: IndexMap::new(),
195 children: IndexMap::new(),
196 parent,
197 destroyed: false,
198 namespace_sinks: IndexMap::new(),
199 next_ns_sink_id: 0,
200 factory: None,
201 factory_args: None,
202 })),
203 }
204 }
205
206 pub fn tag_factory(&self, factory: impl Into<String>, factory_args: Option<serde_json::Value>) {
232 let mut inner = self.inner.borrow_mut();
233 inner.factory = Some(factory.into());
234 inner.factory_args = factory_args;
239 }
240
241 pub(crate) fn from_inner(inner: Rc<RefCell<GraphInner>>) -> Self {
243 Self { inner }
244 }
245
246 #[inline]
248 pub(crate) fn inner_arc(&self) -> &Rc<RefCell<GraphInner>> {
249 &self.inner
250 }
251
252 #[must_use]
254 pub fn is_valid_name(name: &str) -> bool {
255 !name.contains(PATH_SEP) && !name.starts_with("_anon_")
256 }
257
258 #[must_use]
260 pub fn name(&self) -> String {
261 self.inner.borrow_mut().name.clone()
262 }
263
264 pub fn subscribe_namespace_change(&self, sink: NamespaceChangeSink) -> u64 {
270 register_ns_sink(&self.inner, sink)
271 }
272
273 pub fn unsubscribe_namespace_change(&self, id: u64) {
276 unregister_ns_sink(&self.inner, id);
277 }
278
279 #[must_use]
284 pub fn describe(&self, core: &Core) -> GraphDescribeOutput {
285 describe_of(core, &self.inner, None)
286 }
287
288 #[must_use]
290 pub fn describe_with_debug(
291 &self,
292 core: &Core,
293 debug: &dyn DebugBindingBoundary,
294 ) -> GraphDescribeOutput {
295 describe_of(core, &self.inner, Some(debug))
296 }
297
298 #[must_use = "dropping the handle without calling .detach(&core) leaks the topology sub"]
304 pub fn describe_reactive(&self, core: &Core, sink: &DescribeSink) -> ReactiveDescribeHandle {
305 describe_reactive_in(core, &self.inner, sink)
306 }
307
308 #[must_use = "GraphObserveOne is only useful via .subscribe(...) — dropping it silently no-ops"]
311 pub fn observe(&self, path: &str) -> GraphObserveOne {
312 let id = self.node(path);
313 GraphObserveOne::new(self.clone(), id)
314 }
315
316 #[must_use = "GraphObserveAll is only useful via .subscribe(...) — dropping it silently no-ops"]
318 pub fn observe_all(&self) -> GraphObserveAll {
319 GraphObserveAll::new(self.clone())
320 }
321
322 #[must_use = "GraphObserveAllReactive is only useful via .subscribe(...) — dropping it silently no-ops"]
324 pub fn observe_all_reactive(&self) -> GraphObserveAllReactive {
325 GraphObserveAllReactive::new(self.clone())
326 }
327
328 pub fn fire_namespace_change(&self, core: &Core) {
331 fire_ns(core, &self.inner);
332 }
333
334 pub fn add(
342 &self,
343 core: &Core,
344 node_id: NodeId,
345 name: impl Into<String>,
346 ) -> Result<NodeId, NameError> {
347 let name = name.into();
348 validate_name(&name)?;
349 {
350 let mut inner = self.inner.borrow_mut();
351 if inner.destroyed {
352 return Err(NameError::Destroyed);
353 }
354 if inner.names.contains_key(&name) {
355 return Err(NameError::Collision(name));
356 }
357 inner.names.insert(name.clone(), node_id);
358 inner.names_inverse.insert(node_id, name);
359 }
360 self.fire_namespace_change(core);
361 Ok(node_id)
362 }
363
364 #[must_use]
366 pub fn node(&self, path: &str) -> NodeId {
367 self.try_resolve(path)
368 .unwrap_or_else(|| panic!("Graph::node: no node at path `{path}`"))
369 }
370
371 #[must_use]
373 pub fn try_resolve(&self, path: &str) -> Option<NodeId> {
374 self.try_resolve_checked(path).ok().flatten()
375 }
376
377 pub fn try_resolve_checked(&self, path: &str) -> Result<Option<NodeId>, PathError> {
383 resolve_checked(&self.inner, path)
384 }
385
386 #[must_use]
388 pub fn name_of(&self, node_id: NodeId) -> Option<String> {
389 self.inner.borrow_mut().names_inverse.get(&node_id).cloned()
390 }
391
392 #[must_use]
394 pub fn node_count(&self) -> usize {
395 self.inner.borrow_mut().names.len()
396 }
397
398 #[must_use]
400 pub fn node_names(&self) -> Vec<String> {
401 self.inner.borrow_mut().names.keys().cloned().collect()
402 }
403
404 #[must_use]
406 pub fn child_names(&self) -> Vec<String> {
407 self.inner.borrow_mut().children.keys().cloned().collect()
408 }
409
410 #[must_use]
421 pub fn child(&self, name: &str) -> Option<Graph> {
422 self.inner
423 .borrow_mut()
424 .children
425 .get(name)
426 .cloned()
427 .map(Graph::from_inner)
428 }
429
430 #[must_use]
432 pub fn is_destroyed(&self) -> bool {
433 self.inner.borrow_mut().destroyed
434 }
435
436 pub fn state(
443 &self,
444 core: &Core,
445 name: impl Into<String>,
446 initial: Option<HandleId>,
447 ) -> Result<NodeId, NameError> {
448 let id = core
449 .register_state(initial.unwrap_or(graphrefly_core::NO_HANDLE), false)
450 .expect("invariant: register_state has no error variants reachable for caller-controlled inputs");
451 self.add(core, id, name)
452 }
453
454 pub fn derived(
459 &self,
460 core: &Core,
461 name: impl Into<String>,
462 deps: &[NodeId],
463 fn_id: FnId,
464 equals: EqualsMode,
465 ) -> Result<NodeId, NameError> {
466 let id = core
467 .register_derived(deps, fn_id, equals, false)
468 .expect("invariant: caller has validated dep ids before calling register_derived");
469 self.add(core, id, name)
470 }
471
472 pub fn dynamic(
477 &self,
478 core: &Core,
479 name: impl Into<String>,
480 deps: &[NodeId],
481 fn_id: FnId,
482 equals: EqualsMode,
483 ) -> Result<NodeId, NameError> {
484 let id = core
485 .register_dynamic(deps, fn_id, equals, false)
486 .expect("invariant: caller has validated dep ids before calling register_dynamic");
487 self.add(core, id, name)
488 }
489
490 pub fn set(&self, core: &Core, name: &str, handle: HandleId) {
494 let id = self.node(name);
495 core.emit(id, handle);
496 }
497
498 #[must_use]
500 pub fn get(&self, core: &Core, name: &str) -> HandleId {
501 let id = self.node(name);
502 core.cache_of(id)
503 }
504
505 pub fn invalidate_by_name(&self, core: &Core, name: &str) {
507 let id = self.node(name);
508 core.invalidate(id);
509 }
510
511 pub fn complete_by_name(&self, core: &Core, name: &str) {
513 let id = self.node(name);
514 core.complete(id);
515 }
516
517 pub fn error_by_name(&self, core: &Core, name: &str, error_handle: HandleId) {
519 let id = self.node(name);
520 core.error(id, error_handle);
521 }
522
523 pub fn remove(&self, core: &Core, name: &str) -> Result<GraphRemoveAudit, RemoveError> {
531 {
532 let inner = self.inner.borrow_mut();
533 if inner.destroyed {
534 return Err(RemoveError::Destroyed);
535 }
536 if inner.children.contains_key(name) {
537 drop(inner);
538 return self.unmount(core, name).map_err(|e| match e {
539 MountError::Destroyed => RemoveError::Destroyed,
540 _ => RemoveError::NotFound(name.to_owned()),
541 });
542 }
543 }
544 let node_id = {
545 let inner = self.inner.borrow_mut();
546 if inner.destroyed {
547 return Err(RemoveError::Destroyed);
548 }
549 *inner
550 .names
551 .get(name)
552 .ok_or_else(|| RemoveError::NotFound(name.to_owned()))?
553 };
554 core.teardown(node_id);
555 {
556 let mut inner = self.inner.borrow_mut();
557 inner.names.shift_remove(name);
558 inner.names_inverse.shift_remove(&node_id);
559 }
560 self.fire_namespace_change(core);
561 Ok(GraphRemoveAudit {
562 node_count: 1,
563 mount_count: 0,
564 })
565 }
566
567 #[must_use]
572 pub fn edges(&self, core: &Core, recursive: bool) -> Vec<(String, String)> {
573 let names_map = collect_qualified_names_in(&self.inner, "", recursive);
574 edges_in(core, &self.inner, "", recursive, &names_map)
575 }
576
577 #[must_use = "the SubscriptionId must be kept to later unsubscribe; dropping it leaks the sink"]
583 pub fn subscribe(&self, core: &Core, node_id: NodeId, sink: Sink) -> SubscriptionId {
584 core.subscribe(node_id, sink)
585 }
586
587 pub fn unsubscribe(&self, core: &Core, node_id: NodeId, sub_id: SubscriptionId) {
589 core.unsubscribe(node_id, sub_id);
590 }
591
592 pub fn unsubscribe_topology(&self, core: &Core, id: TopologySubscriptionId) {
594 core.unsubscribe_topology(id);
595 }
596
597 pub fn emit(&self, core: &Core, node_id: NodeId, new_handle: HandleId) {
599 core.emit(node_id, new_handle);
600 }
601
602 #[must_use]
604 pub fn cache_of(&self, core: &Core, node_id: NodeId) -> HandleId {
605 core.cache_of(node_id)
606 }
607
608 #[must_use]
610 pub fn has_fired_once(&self, core: &Core, node_id: NodeId) -> bool {
611 core.has_fired_once(node_id)
612 }
613
614 pub fn complete(&self, core: &Core, node_id: NodeId) {
616 core.complete(node_id);
617 }
618
619 pub fn error(&self, core: &Core, node_id: NodeId, error_handle: HandleId) {
621 core.error(node_id, error_handle);
622 }
623
624 pub fn teardown(&self, core: &Core, node_id: NodeId) {
626 core.teardown(node_id);
627 }
628
629 pub fn invalidate(&self, core: &Core, node_id: NodeId) {
631 core.invalidate(node_id);
632 }
633
634 pub fn pause(&self, core: &Core, node_id: NodeId, lock_id: LockId) -> Result<(), PauseError> {
639 core.pause(node_id, lock_id)
640 }
641
642 pub fn resume(
647 &self,
648 core: &Core,
649 node_id: NodeId,
650 lock_id: LockId,
651 ) -> Result<Option<ResumeReport>, PauseError> {
652 core.resume(node_id, lock_id)
653 }
654
655 #[must_use]
657 pub fn alloc_lock_id(&self, core: &Core) -> LockId {
658 core.alloc_lock_id()
659 }
660
661 pub fn set_deps(
673 &self,
674 core: &Core,
675 n: NodeId,
676 new_deps: &[NodeId],
677 ) -> Result<(), SetDepsError> {
678 core.set_deps(n, new_deps)
679 }
680
681 pub fn set_resubscribable(&self, core: &Core, node_id: NodeId, resubscribable: bool) {
683 core.set_resubscribable(node_id, resubscribable);
684 }
685
686 pub fn add_meta_companion(&self, core: &Core, parent: NodeId, companion: NodeId) {
688 core.add_meta_companion(parent, companion);
689 }
690
691 pub fn batch<F: FnOnce()>(&self, core: &Core, f: F) {
693 core.batch(f);
694 }
695
696 pub fn signal(&self, core: &Core, kind: SignalKind) {
700 match kind {
701 SignalKind::Invalidate => self.signal_invalidate(core),
702 SignalKind::Pause(lock_id) => {
703 for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
704 let _ = core.pause(id, lock_id);
705 }
706 }
707 SignalKind::Resume(lock_id) => {
708 for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
709 let _ = core.resume(id, lock_id);
710 }
711 }
712 SignalKind::Complete => {
713 for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
714 core.complete(id);
715 }
716 }
717 SignalKind::Error(h) => {
718 let ids = collect_signal_ids_with_meta_filter(core, &self.inner);
719 for _ in 1..ids.len() {
722 core.binding_ptr().retain_handle(h);
723 }
724 for id in ids {
725 core.error(id, h);
726 }
727 }
728 }
729 }
730
731 pub fn signal_invalidate(&self, core: &Core) {
735 let to_invalidate = collect_signal_invalidate_ids(core, &self.inner);
736 for id in to_invalidate {
737 core.invalidate(id);
738 }
739 }
740
741 pub fn destroy(&self, core: &Core) {
746 destroy_subtree(core, &self.inner);
747 }
748
749 pub fn mount(
757 &self,
758 core: &Core,
759 name: impl Into<String>,
760 child: &Graph,
761 ) -> Result<Graph, MountError> {
762 crate::mount::mount(core, &self.inner, name.into(), child)
763 }
764
765 pub fn mount_new(&self, core: &Core, name: impl Into<String>) -> Result<Graph, MountError> {
770 crate::mount::mount_new(core, &self.inner, name.into())
771 }
772
773 pub fn mount_with<F: FnOnce(&Graph)>(
778 &self,
779 core: &Core,
780 name: impl Into<String>,
781 builder: F,
782 ) -> Result<Graph, MountError> {
783 let child = self.mount_new(core, name)?;
784 builder(&child);
785 Ok(child)
786 }
787
788 pub fn unmount(&self, core: &Core, name: &str) -> Result<GraphRemoveAudit, MountError> {
793 crate::mount::unmount(core, &self.inner, name)
794 }
795
796 #[must_use]
799 pub fn ancestors(&self, include_self: bool) -> Vec<Graph> {
800 crate::mount::ancestors(&self.inner, include_self)
801 }
802}
803
804fn validate_name(name: &str) -> Result<(), NameError> {
809 if name.contains(PATH_SEP) {
810 Err(NameError::InvalidName(name.to_owned()))
811 } else if name.starts_with("_anon_") {
812 Err(NameError::ReservedPrefix(name.to_owned()))
813 } else {
814 Ok(())
815 }
816}
817
818pub(crate) fn register_ns_sink(
820 inner_arc: &Rc<RefCell<GraphInner>>,
821 sink: NamespaceChangeSink,
822) -> u64 {
823 let mut inner = inner_arc.borrow_mut();
824 let id = inner.next_ns_sink_id;
825 inner.next_ns_sink_id += 1;
826 inner.namespace_sinks.insert(id, sink);
827 id
828}
829
830pub(crate) fn unregister_ns_sink(inner_arc: &Rc<RefCell<GraphInner>>, id: u64) {
832 inner_arc.borrow_mut().namespace_sinks.shift_remove(&id);
833}
834
835pub(crate) fn resolve_checked(
837 inner_arc: &Rc<RefCell<GraphInner>>,
838 path: &str,
839) -> Result<Option<NodeId>, PathError> {
840 if path.is_empty() {
841 return Err(PathError::Empty);
842 }
843 let inner = inner_arc.borrow_mut();
844 if inner.destroyed {
845 return Err(PathError::Destroyed);
846 }
847 let segments: Vec<&str> = path.split(PATH_SEP).collect();
848 let first = segments[0];
849 if first == ".." {
850 let parent_weak = inner.parent.as_ref().ok_or(PathError::NoParent)?;
851 let parent_inner = parent_weak.upgrade().ok_or(PathError::NoParent)?;
852 drop(inner);
853 if segments.len() == 1 {
854 return Ok(None);
855 }
856 let rest = segments[1..].join(PATH_SEP);
857 resolve_checked(&parent_inner, &rest)
858 } else if segments.len() > 1 {
859 let child = inner
860 .children
861 .get(first)
862 .cloned()
863 .ok_or_else(|| PathError::ChildNotFound(first.to_string()))?;
864 drop(inner);
865 let rest = segments[1..].join(PATH_SEP);
866 resolve_checked(&child, &rest)
867 } else {
868 Ok(inner.names.get(first).copied())
869 }
870}
871
872pub(crate) fn fire_ns(core: &Core, inner_arc: &Rc<RefCell<GraphInner>>) {
875 let sinks: Vec<NamespaceChangeSink> = {
876 let inner = inner_arc.borrow_mut();
877 inner.namespace_sinks.values().cloned().collect()
878 };
879 for sink in sinks {
880 sink(core);
881 }
882}
883
884pub(crate) fn destroy_subtree(core: &Core, inner_arc: &Rc<RefCell<GraphInner>>) {
887 let (own_ids, child_clones) = {
888 let mut inner = inner_arc.borrow_mut();
889 if inner.destroyed {
890 return; }
892 inner.destroyed = true;
893 let own = inner.names.values().copied().collect::<Vec<_>>();
894 let kids = inner.children.values().cloned().collect::<Vec<_>>();
895 (own, kids)
896 };
897 for child in &child_clones {
898 destroy_subtree(core, child);
899 }
900 for id in own_ids {
901 core.teardown(id);
902 }
903 {
904 let mut inner = inner_arc.borrow_mut();
905 inner.names.clear();
906 inner.names_inverse.clear();
907 inner.children.clear();
908 }
909 fire_ns(core, inner_arc);
913 inner_arc.borrow_mut().namespace_sinks.clear();
921}
922
923fn collect_signal_ids_with_meta_filter(core: &Core, root: &Rc<RefCell<GraphInner>>) -> Vec<NodeId> {
927 let mut out: Vec<NodeId> = Vec::new();
928 let mut worklist: Vec<Rc<RefCell<GraphInner>>> = vec![root.clone()];
929 while let Some(inner_arc) = worklist.pop() {
930 let (own_ids, meta_set, child_clones) = {
931 let inner = inner_arc.borrow_mut();
932 if inner.destroyed {
933 continue;
934 }
935 let mut meta_set: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
936 for &parent_id in inner.names.values() {
937 for child_id in core.meta_companions_of(parent_id) {
938 meta_set.insert(child_id);
939 }
940 }
941 (
942 inner.names.values().copied().collect::<Vec<_>>(),
943 meta_set,
944 inner.children.values().cloned().collect::<Vec<_>>(),
945 )
946 };
947 for id in own_ids {
948 if meta_set.contains(&id) {
949 continue;
950 }
951 out.push(id);
952 }
953 worklist.extend(child_clones);
954 }
955 out
956}
957
958fn collect_signal_invalidate_ids(core: &Core, root: &Rc<RefCell<GraphInner>>) -> Vec<NodeId> {
960 let mut out: Vec<NodeId> = Vec::new();
961 let mut worklist: Vec<Rc<RefCell<GraphInner>>> = vec![root.clone()];
962 while let Some(inner_arc) = worklist.pop() {
963 let (own_ids, meta_set, child_clones) = {
964 let inner = inner_arc.borrow_mut();
965 if inner.destroyed {
966 continue;
967 }
968 let mut meta_set: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
969 for &parent_id in inner.names.values() {
970 for child_id in core.meta_companions_of(parent_id) {
971 meta_set.insert(child_id);
972 }
973 }
974 (
975 inner.names.values().copied().collect::<Vec<_>>(),
976 meta_set,
977 inner.children.values().cloned().collect::<Vec<_>>(),
978 )
979 };
980 for id in own_ids {
981 if meta_set.contains(&id) {
982 continue;
983 }
984 out.push(id);
985 }
986 worklist.extend(child_clones);
987 }
988 out
989}
990
991pub(crate) fn collect_qualified_names_in(
994 inner_arc: &Rc<RefCell<GraphInner>>,
995 prefix: &str,
996 recursive: bool,
997) -> IndexMap<NodeId, String> {
998 let inner = inner_arc.borrow_mut();
999 let mut map: IndexMap<NodeId, String> = inner
1000 .names
1001 .iter()
1002 .map(|(n, id)| (*id, format!("{prefix}{n}")))
1003 .collect();
1004 let children: Vec<(String, Rc<RefCell<GraphInner>>)> = if recursive {
1005 inner
1006 .children
1007 .iter()
1008 .map(|(n, g)| (n.clone(), g.clone()))
1009 .collect()
1010 } else {
1011 Vec::new()
1012 };
1013 drop(inner);
1014 for (child_name, child_inner) in children {
1015 let child_prefix = format!("{prefix}{child_name}::");
1016 let child_map = collect_qualified_names_in(&child_inner, &child_prefix, true);
1017 for (id, name) in child_map {
1018 map.entry(id).or_insert(name);
1019 }
1020 }
1021 map
1022}
1023
1024pub(crate) fn edges_in(
1026 core: &Core,
1027 inner_arc: &Rc<RefCell<GraphInner>>,
1028 prefix: &str,
1029 recursive: bool,
1030 names_map: &IndexMap<NodeId, String>,
1031) -> Vec<(String, String)> {
1032 let inner = inner_arc.borrow_mut();
1033 let qualified: Vec<(String, NodeId)> = inner
1034 .names
1035 .iter()
1036 .map(|(n, id)| (format!("{prefix}{n}"), *id))
1037 .collect();
1038 let children: Vec<(String, Rc<RefCell<GraphInner>>)> = if recursive {
1039 inner
1040 .children
1041 .iter()
1042 .map(|(n, g)| (n.clone(), g.clone()))
1043 .collect()
1044 } else {
1045 Vec::new()
1046 };
1047 drop(inner);
1048 let mut result: Vec<(String, String)> = Vec::new();
1049 for (to_name, id) in &qualified {
1050 let dep_ids = core.deps_of(*id);
1051 for dep_id in dep_ids {
1052 let from_name = names_map
1053 .get(&dep_id)
1054 .cloned()
1055 .unwrap_or_else(|| format!("{prefix}_anon_{}", dep_id.raw()));
1056 result.push((from_name, to_name.clone()));
1057 }
1058 }
1059 for (child_name, child_inner) in children {
1060 let child_prefix = format!("{prefix}{child_name}::");
1061 result.extend(edges_in(core, &child_inner, &child_prefix, true, names_map));
1062 }
1063 result
1064}
1065
1066