use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use graphrefly_core::{
Core, EqualsMode, FnId, HandleId, LockId, NodeId, PauseError, ResumeReport, SetDepsError, Sink,
SubscriptionId, TopologySubscriptionId,
};
use indexmap::IndexMap;
use crate::debug::DebugBindingBoundary;
use crate::describe::{
describe_of, describe_reactive_in, DescribeSink, GraphDescribeOutput, ReactiveDescribeHandle,
};
use crate::mount::{GraphRemoveAudit, MountError};
use crate::observe::{GraphObserveAll, GraphObserveAllReactive, GraphObserveOne};
pub(crate) const PATH_SEP: &str = "::";
#[derive(Debug, thiserror::Error)]
pub enum RemoveError {
#[error("Graph::remove: name `{0}` not found (neither a node nor a mounted subgraph)")]
NotFound(String),
#[error("Graph::remove: graph has been destroyed")]
Destroyed,
}
#[derive(Debug, Clone, Copy)]
pub enum SignalKind {
Invalidate,
Pause(LockId),
Resume(LockId),
Complete,
Error(HandleId),
}
#[derive(Debug, thiserror::Error)]
pub enum PathError {
#[error("Path is empty")]
Empty,
#[error("Path segment `..` used on root graph (no parent)")]
NoParent,
#[error("Path segment `{0}` does not match any child graph")]
ChildNotFound(String),
#[error("Graph has been destroyed")]
Destroyed,
}
#[derive(Debug, thiserror::Error)]
pub enum NameError {
#[error("Graph::add: name `{0}` already registered in this graph")]
Collision(String),
#[error("Graph: name `{0}` may not contain the `::` path separator")]
InvalidName(String),
#[error("Graph: name `{0}` uses the reserved `_anon_` prefix (collides with anonymous node describe format)")]
ReservedPrefix(String),
#[error("Graph: graph has been destroyed; further registration refused")]
Destroyed,
}
pub(crate) type NamespaceChangeSink = Arc<dyn Fn(&Core)>;
pub struct GraphInner {
pub(crate) name: String,
pub(crate) names: IndexMap<String, NodeId>,
pub(crate) names_inverse: IndexMap<NodeId, String>,
pub(crate) children: IndexMap<String, Rc<RefCell<GraphInner>>>,
pub(crate) parent: Option<Weak<RefCell<GraphInner>>>,
pub(crate) destroyed: bool,
pub(crate) namespace_sinks: IndexMap<u64, NamespaceChangeSink>,
pub(crate) next_ns_sink_id: u64,
}
#[derive(Clone)]
pub struct Graph {
pub(crate) inner: Rc<RefCell<GraphInner>>,
}
impl std::fmt::Debug for Graph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let inner = self.inner.borrow_mut();
f.debug_struct("Graph")
.field("name", &inner.name)
.field("node_count", &inner.names.len())
.field("subgraph_count", &inner.children.len())
.field("destroyed", &inner.destroyed)
.finish_non_exhaustive()
}
}
#[allow(clippy::missing_panics_doc, clippy::must_use_candidate)]
impl Graph {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self::with_parent(name.into(), None)
}
pub(crate) fn with_parent(name: String, parent: Option<Weak<RefCell<GraphInner>>>) -> Self {
Self {
inner: Rc::new(RefCell::new(GraphInner {
name,
names: IndexMap::new(),
names_inverse: IndexMap::new(),
children: IndexMap::new(),
parent,
destroyed: false,
namespace_sinks: IndexMap::new(),
next_ns_sink_id: 0,
})),
}
}
pub(crate) fn from_inner(inner: Rc<RefCell<GraphInner>>) -> Self {
Self { inner }
}
#[inline]
pub(crate) fn inner_arc(&self) -> &Rc<RefCell<GraphInner>> {
&self.inner
}
#[must_use]
pub fn is_valid_name(name: &str) -> bool {
!name.contains(PATH_SEP) && !name.starts_with("_anon_")
}
#[must_use]
pub fn name(&self) -> String {
self.inner.borrow_mut().name.clone()
}
pub fn subscribe_namespace_change(&self, sink: NamespaceChangeSink) -> u64 {
register_ns_sink(&self.inner, sink)
}
pub fn unsubscribe_namespace_change(&self, id: u64) {
unregister_ns_sink(&self.inner, id);
}
#[must_use]
pub fn describe(&self, core: &Core) -> GraphDescribeOutput {
describe_of(core, &self.inner, None)
}
#[must_use]
pub fn describe_with_debug(
&self,
core: &Core,
debug: &dyn DebugBindingBoundary,
) -> GraphDescribeOutput {
describe_of(core, &self.inner, Some(debug))
}
#[must_use = "dropping the handle without calling .detach(&core) leaks the topology sub"]
pub fn describe_reactive(&self, core: &Core, sink: &DescribeSink) -> ReactiveDescribeHandle {
describe_reactive_in(core, &self.inner, sink)
}
#[must_use = "GraphObserveOne is only useful via .subscribe(...) — dropping it silently no-ops"]
pub fn observe(&self, path: &str) -> GraphObserveOne {
let id = self.node(path);
GraphObserveOne::new(self.clone(), id)
}
#[must_use = "GraphObserveAll is only useful via .subscribe(...) — dropping it silently no-ops"]
pub fn observe_all(&self) -> GraphObserveAll {
GraphObserveAll::new(self.clone())
}
#[must_use = "GraphObserveAllReactive is only useful via .subscribe(...) — dropping it silently no-ops"]
pub fn observe_all_reactive(&self) -> GraphObserveAllReactive {
GraphObserveAllReactive::new(self.clone())
}
pub fn fire_namespace_change(&self, core: &Core) {
fire_ns(core, &self.inner);
}
pub fn add(
&self,
core: &Core,
node_id: NodeId,
name: impl Into<String>,
) -> Result<NodeId, NameError> {
let name = name.into();
validate_name(&name)?;
{
let mut inner = self.inner.borrow_mut();
if inner.destroyed {
return Err(NameError::Destroyed);
}
if inner.names.contains_key(&name) {
return Err(NameError::Collision(name));
}
inner.names.insert(name.clone(), node_id);
inner.names_inverse.insert(node_id, name);
}
self.fire_namespace_change(core);
Ok(node_id)
}
#[must_use]
pub fn node(&self, path: &str) -> NodeId {
self.try_resolve(path)
.unwrap_or_else(|| panic!("Graph::node: no node at path `{path}`"))
}
#[must_use]
pub fn try_resolve(&self, path: &str) -> Option<NodeId> {
self.try_resolve_checked(path).ok().flatten()
}
pub fn try_resolve_checked(&self, path: &str) -> Result<Option<NodeId>, PathError> {
resolve_checked(&self.inner, path)
}
#[must_use]
pub fn name_of(&self, node_id: NodeId) -> Option<String> {
self.inner.borrow_mut().names_inverse.get(&node_id).cloned()
}
#[must_use]
pub fn node_count(&self) -> usize {
self.inner.borrow_mut().names.len()
}
#[must_use]
pub fn node_names(&self) -> Vec<String> {
self.inner.borrow_mut().names.keys().cloned().collect()
}
#[must_use]
pub fn child_names(&self) -> Vec<String> {
self.inner.borrow_mut().children.keys().cloned().collect()
}
#[must_use]
pub fn is_destroyed(&self) -> bool {
self.inner.borrow_mut().destroyed
}
pub fn state(
&self,
core: &Core,
name: impl Into<String>,
initial: Option<HandleId>,
) -> Result<NodeId, NameError> {
let id = core
.register_state(initial.unwrap_or(graphrefly_core::NO_HANDLE), false)
.expect("invariant: register_state has no error variants reachable for caller-controlled inputs");
self.add(core, id, name)
}
pub fn derived(
&self,
core: &Core,
name: impl Into<String>,
deps: &[NodeId],
fn_id: FnId,
equals: EqualsMode,
) -> Result<NodeId, NameError> {
let id = core
.register_derived(deps, fn_id, equals, false)
.expect("invariant: caller has validated dep ids before calling register_derived");
self.add(core, id, name)
}
pub fn dynamic(
&self,
core: &Core,
name: impl Into<String>,
deps: &[NodeId],
fn_id: FnId,
equals: EqualsMode,
) -> Result<NodeId, NameError> {
let id = core
.register_dynamic(deps, fn_id, equals, false)
.expect("invariant: caller has validated dep ids before calling register_dynamic");
self.add(core, id, name)
}
pub fn set(&self, core: &Core, name: &str, handle: HandleId) {
let id = self.node(name);
core.emit(id, handle);
}
#[must_use]
pub fn get(&self, core: &Core, name: &str) -> HandleId {
let id = self.node(name);
core.cache_of(id)
}
pub fn invalidate_by_name(&self, core: &Core, name: &str) {
let id = self.node(name);
core.invalidate(id);
}
pub fn complete_by_name(&self, core: &Core, name: &str) {
let id = self.node(name);
core.complete(id);
}
pub fn error_by_name(&self, core: &Core, name: &str, error_handle: HandleId) {
let id = self.node(name);
core.error(id, error_handle);
}
pub fn remove(&self, core: &Core, name: &str) -> Result<GraphRemoveAudit, RemoveError> {
{
let inner = self.inner.borrow_mut();
if inner.destroyed {
return Err(RemoveError::Destroyed);
}
if inner.children.contains_key(name) {
drop(inner);
return self.unmount(core, name).map_err(|e| match e {
MountError::Destroyed => RemoveError::Destroyed,
_ => RemoveError::NotFound(name.to_owned()),
});
}
}
let node_id = {
let inner = self.inner.borrow_mut();
if inner.destroyed {
return Err(RemoveError::Destroyed);
}
*inner
.names
.get(name)
.ok_or_else(|| RemoveError::NotFound(name.to_owned()))?
};
core.teardown(node_id);
{
let mut inner = self.inner.borrow_mut();
inner.names.shift_remove(name);
inner.names_inverse.shift_remove(&node_id);
}
self.fire_namespace_change(core);
Ok(GraphRemoveAudit {
node_count: 1,
mount_count: 0,
})
}
#[must_use]
pub fn edges(&self, core: &Core, recursive: bool) -> Vec<(String, String)> {
let names_map = collect_qualified_names_in(&self.inner, "", recursive);
edges_in(core, &self.inner, "", recursive, &names_map)
}
#[must_use = "the SubscriptionId must be kept to later unsubscribe; dropping it leaks the sink"]
pub fn subscribe(&self, core: &Core, node_id: NodeId, sink: Sink) -> SubscriptionId {
core.subscribe(node_id, sink)
}
pub fn unsubscribe(&self, core: &Core, node_id: NodeId, sub_id: SubscriptionId) {
core.unsubscribe(node_id, sub_id);
}
pub fn unsubscribe_topology(&self, core: &Core, id: TopologySubscriptionId) {
core.unsubscribe_topology(id);
}
pub fn emit(&self, core: &Core, node_id: NodeId, new_handle: HandleId) {
core.emit(node_id, new_handle);
}
#[must_use]
pub fn cache_of(&self, core: &Core, node_id: NodeId) -> HandleId {
core.cache_of(node_id)
}
#[must_use]
pub fn has_fired_once(&self, core: &Core, node_id: NodeId) -> bool {
core.has_fired_once(node_id)
}
pub fn complete(&self, core: &Core, node_id: NodeId) {
core.complete(node_id);
}
pub fn error(&self, core: &Core, node_id: NodeId, error_handle: HandleId) {
core.error(node_id, error_handle);
}
pub fn teardown(&self, core: &Core, node_id: NodeId) {
core.teardown(node_id);
}
pub fn invalidate(&self, core: &Core, node_id: NodeId) {
core.invalidate(node_id);
}
pub fn pause(&self, core: &Core, node_id: NodeId, lock_id: LockId) -> Result<(), PauseError> {
core.pause(node_id, lock_id)
}
pub fn resume(
&self,
core: &Core,
node_id: NodeId,
lock_id: LockId,
) -> Result<Option<ResumeReport>, PauseError> {
core.resume(node_id, lock_id)
}
#[must_use]
pub fn alloc_lock_id(&self, core: &Core) -> LockId {
core.alloc_lock_id()
}
pub fn set_deps(
&self,
core: &Core,
n: NodeId,
new_deps: &[NodeId],
) -> Result<(), SetDepsError> {
core.set_deps(n, new_deps)
}
pub fn set_resubscribable(&self, core: &Core, node_id: NodeId, resubscribable: bool) {
core.set_resubscribable(node_id, resubscribable);
}
pub fn add_meta_companion(&self, core: &Core, parent: NodeId, companion: NodeId) {
core.add_meta_companion(parent, companion);
}
pub fn batch<F: FnOnce()>(&self, core: &Core, f: F) {
core.batch(f);
}
pub fn signal(&self, core: &Core, kind: SignalKind) {
match kind {
SignalKind::Invalidate => self.signal_invalidate(core),
SignalKind::Pause(lock_id) => {
for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
let _ = core.pause(id, lock_id);
}
}
SignalKind::Resume(lock_id) => {
for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
let _ = core.resume(id, lock_id);
}
}
SignalKind::Complete => {
for id in collect_signal_ids_with_meta_filter(core, &self.inner) {
core.complete(id);
}
}
SignalKind::Error(h) => {
let ids = collect_signal_ids_with_meta_filter(core, &self.inner);
for _ in 1..ids.len() {
core.binding_ptr().retain_handle(h);
}
for id in ids {
core.error(id, h);
}
}
}
}
pub fn signal_invalidate(&self, core: &Core) {
let to_invalidate = collect_signal_invalidate_ids(core, &self.inner);
for id in to_invalidate {
core.invalidate(id);
}
}
pub fn destroy(&self, core: &Core) {
destroy_subtree(core, &self.inner);
}
pub fn mount(
&self,
core: &Core,
name: impl Into<String>,
child: &Graph,
) -> Result<Graph, MountError> {
crate::mount::mount(core, &self.inner, name.into(), child)
}
pub fn mount_new(&self, core: &Core, name: impl Into<String>) -> Result<Graph, MountError> {
crate::mount::mount_new(core, &self.inner, name.into())
}
pub fn mount_with<F: FnOnce(&Graph)>(
&self,
core: &Core,
name: impl Into<String>,
builder: F,
) -> Result<Graph, MountError> {
let child = self.mount_new(core, name)?;
builder(&child);
Ok(child)
}
pub fn unmount(&self, core: &Core, name: &str) -> Result<GraphRemoveAudit, MountError> {
crate::mount::unmount(core, &self.inner, name)
}
#[must_use]
pub fn ancestors(&self, include_self: bool) -> Vec<Graph> {
crate::mount::ancestors(&self.inner, include_self)
}
}
fn validate_name(name: &str) -> Result<(), NameError> {
if name.contains(PATH_SEP) {
Err(NameError::InvalidName(name.to_owned()))
} else if name.starts_with("_anon_") {
Err(NameError::ReservedPrefix(name.to_owned()))
} else {
Ok(())
}
}
pub(crate) fn register_ns_sink(
inner_arc: &Rc<RefCell<GraphInner>>,
sink: NamespaceChangeSink,
) -> u64 {
let mut inner = inner_arc.borrow_mut();
let id = inner.next_ns_sink_id;
inner.next_ns_sink_id += 1;
inner.namespace_sinks.insert(id, sink);
id
}
pub(crate) fn unregister_ns_sink(inner_arc: &Rc<RefCell<GraphInner>>, id: u64) {
inner_arc.borrow_mut().namespace_sinks.shift_remove(&id);
}
pub(crate) fn resolve_checked(
inner_arc: &Rc<RefCell<GraphInner>>,
path: &str,
) -> Result<Option<NodeId>, PathError> {
if path.is_empty() {
return Err(PathError::Empty);
}
let inner = inner_arc.borrow_mut();
if inner.destroyed {
return Err(PathError::Destroyed);
}
let segments: Vec<&str> = path.split(PATH_SEP).collect();
let first = segments[0];
if first == ".." {
let parent_weak = inner.parent.as_ref().ok_or(PathError::NoParent)?;
let parent_inner = parent_weak.upgrade().ok_or(PathError::NoParent)?;
drop(inner);
if segments.len() == 1 {
return Ok(None);
}
let rest = segments[1..].join(PATH_SEP);
resolve_checked(&parent_inner, &rest)
} else if segments.len() > 1 {
let child = inner
.children
.get(first)
.cloned()
.ok_or_else(|| PathError::ChildNotFound(first.to_string()))?;
drop(inner);
let rest = segments[1..].join(PATH_SEP);
resolve_checked(&child, &rest)
} else {
Ok(inner.names.get(first).copied())
}
}
pub(crate) fn fire_ns(core: &Core, inner_arc: &Rc<RefCell<GraphInner>>) {
let sinks: Vec<NamespaceChangeSink> = {
let inner = inner_arc.borrow_mut();
inner.namespace_sinks.values().cloned().collect()
};
for sink in sinks {
sink(core);
}
}
pub(crate) fn destroy_subtree(core: &Core, inner_arc: &Rc<RefCell<GraphInner>>) {
let (own_ids, child_clones) = {
let mut inner = inner_arc.borrow_mut();
if inner.destroyed {
return; }
inner.destroyed = true;
let own = inner.names.values().copied().collect::<Vec<_>>();
let kids = inner.children.values().cloned().collect::<Vec<_>>();
(own, kids)
};
for child in &child_clones {
destroy_subtree(core, child);
}
for id in own_ids {
core.teardown(id);
}
{
let mut inner = inner_arc.borrow_mut();
inner.names.clear();
inner.names_inverse.clear();
inner.children.clear();
}
fire_ns(core, inner_arc);
inner_arc.borrow_mut().namespace_sinks.clear();
}
fn collect_signal_ids_with_meta_filter(core: &Core, root: &Rc<RefCell<GraphInner>>) -> Vec<NodeId> {
let mut out: Vec<NodeId> = Vec::new();
let mut worklist: Vec<Rc<RefCell<GraphInner>>> = vec![root.clone()];
while let Some(inner_arc) = worklist.pop() {
let (own_ids, meta_set, child_clones) = {
let inner = inner_arc.borrow_mut();
if inner.destroyed {
continue;
}
let mut meta_set: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
for &parent_id in inner.names.values() {
for child_id in core.meta_companions_of(parent_id) {
meta_set.insert(child_id);
}
}
(
inner.names.values().copied().collect::<Vec<_>>(),
meta_set,
inner.children.values().cloned().collect::<Vec<_>>(),
)
};
for id in own_ids {
if meta_set.contains(&id) {
continue;
}
out.push(id);
}
worklist.extend(child_clones);
}
out
}
fn collect_signal_invalidate_ids(core: &Core, root: &Rc<RefCell<GraphInner>>) -> Vec<NodeId> {
let mut out: Vec<NodeId> = Vec::new();
let mut worklist: Vec<Rc<RefCell<GraphInner>>> = vec![root.clone()];
while let Some(inner_arc) = worklist.pop() {
let (own_ids, meta_set, child_clones) = {
let inner = inner_arc.borrow_mut();
if inner.destroyed {
continue;
}
let mut meta_set: std::collections::HashSet<NodeId> = std::collections::HashSet::new();
for &parent_id in inner.names.values() {
for child_id in core.meta_companions_of(parent_id) {
meta_set.insert(child_id);
}
}
(
inner.names.values().copied().collect::<Vec<_>>(),
meta_set,
inner.children.values().cloned().collect::<Vec<_>>(),
)
};
for id in own_ids {
if meta_set.contains(&id) {
continue;
}
out.push(id);
}
worklist.extend(child_clones);
}
out
}
pub(crate) fn collect_qualified_names_in(
inner_arc: &Rc<RefCell<GraphInner>>,
prefix: &str,
recursive: bool,
) -> IndexMap<NodeId, String> {
let inner = inner_arc.borrow_mut();
let mut map: IndexMap<NodeId, String> = inner
.names
.iter()
.map(|(n, id)| (*id, format!("{prefix}{n}")))
.collect();
let children: Vec<(String, Rc<RefCell<GraphInner>>)> = if recursive {
inner
.children
.iter()
.map(|(n, g)| (n.clone(), g.clone()))
.collect()
} else {
Vec::new()
};
drop(inner);
for (child_name, child_inner) in children {
let child_prefix = format!("{prefix}{child_name}::");
let child_map = collect_qualified_names_in(&child_inner, &child_prefix, true);
for (id, name) in child_map {
map.entry(id).or_insert(name);
}
}
map
}
pub(crate) fn edges_in(
core: &Core,
inner_arc: &Rc<RefCell<GraphInner>>,
prefix: &str,
recursive: bool,
names_map: &IndexMap<NodeId, String>,
) -> Vec<(String, String)> {
let inner = inner_arc.borrow_mut();
let qualified: Vec<(String, NodeId)> = inner
.names
.iter()
.map(|(n, id)| (format!("{prefix}{n}"), *id))
.collect();
let children: Vec<(String, Rc<RefCell<GraphInner>>)> = if recursive {
inner
.children
.iter()
.map(|(n, g)| (n.clone(), g.clone()))
.collect()
} else {
Vec::new()
};
drop(inner);
let mut result: Vec<(String, String)> = Vec::new();
for (to_name, id) in &qualified {
let dep_ids = core.deps_of(*id);
for dep_id in dep_ids {
let from_name = names_map
.get(&dep_id)
.cloned()
.unwrap_or_else(|| format!("{prefix}_anon_{}", dep_id.raw()));
result.push((from_name, to_name.clone()));
}
}
for (child_name, child_inner) in children {
let child_prefix = format!("{prefix}{child_name}::");
result.extend(edges_in(core, &child_inner, &child_prefix, true, names_map));
}
result
}