diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 36eb417ca3..0350c6b421 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -323,6 +323,7 @@ pub(crate) fn property_from_type( Type::Generic(_) => vec![TextLabel::new("Generic Type (Not Supported)").widget_instance()].into(), Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context), Type::Future(out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context), + Type::Ref(inner) => return property_from_type(node_id, index, inner, number_options, unit, display_decimal_places, step, context), }; extra_widgets.push(widgets); diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index c3af90cf23..a16fa45f90 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -309,6 +309,7 @@ macro_rules! tagged_value { pub fn from_type(input: &Type) -> Option { match input { Type::Generic(_) => None, + Type::Ref(_) => None, Type::Concrete(concrete_type) => { let name = concrete_type.name.as_ref(); // TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types @@ -569,6 +570,7 @@ impl TaggedValue { match ty { Type::Generic(_) => None, + Type::Ref(_) => None, Type::Concrete(concrete_type) => { let ty = concrete_type.id?; use std::any::TypeId; diff --git a/node-graph/libraries/core-types/src/gpoll.rs b/node-graph/libraries/core-types/src/gpoll.rs new file mode 100644 index 0000000000..3d670647db --- /dev/null +++ b/node-graph/libraries/core-types/src/gpoll.rs @@ -0,0 +1,222 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErrorKind { + Node(&'static str), + ArenaExhausted, +} + +impl PartialEq<&str> for ErrorKind { + fn eq(&self, other: &&str) -> bool { + matches!(self, ErrorKind::Node(kind) if kind == other) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GraphError { + pub kind: ErrorKind, + pub trace: Vec, +} + +impl GraphError { + pub fn new(kind: &'static str) -> Self { + Self { + kind: ErrorKind::Node(kind), + trace: Vec::new(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum GPoll { + Pending, + Final(T), + Partial(T), + Fallback(Box<(T, GraphError)>), + Error(Box), +} + +impl GPoll { + #[inline(always)] + pub fn map(self, f: impl FnOnce(T) -> U) -> GPoll { + match self { + GPoll::Pending => GPoll::Pending, + GPoll::Final(value) => GPoll::Final(f(value)), + GPoll::Partial(value) => GPoll::Partial(f(value)), + GPoll::Fallback(boxed) => { + let (value, e) = *boxed; + GPoll::Fallback(Box::new((f(value), e))) + } + GPoll::Error(e) => GPoll::Error(e), + } + } + + #[inline(always)] + pub fn and_then(self, f: impl FnOnce(T) -> GPoll) -> GPoll { + match self { + GPoll::Pending => GPoll::Pending, + GPoll::Final(value) => f(value), + GPoll::Partial(value) => match f(value) { + GPoll::Final(result) => GPoll::Partial(result), + other => other, + }, + GPoll::Fallback(boxed) => { + let (value, e) = *boxed; + match f(value) { + GPoll::Pending => GPoll::Pending, + GPoll::Final(result) | GPoll::Partial(result) => GPoll::Fallback(Box::new((result, e))), + GPoll::Fallback(inner) => { + let (result, _) = *inner; + GPoll::Fallback(Box::new((result, e))) + } + GPoll::Error(inner) => GPoll::Error(inner), + } + } + GPoll::Error(e) => GPoll::Error(e), + } + } + + #[inline(always)] + pub fn zip(self, other: GPoll) -> GPoll<(T, U)> { + match (self, other) { + (GPoll::Error(e), _) | (_, GPoll::Error(e)) => GPoll::Error(e), + (GPoll::Pending, _) | (_, GPoll::Pending) => GPoll::Pending, + (GPoll::Final(a), GPoll::Final(b)) => GPoll::Final((a, b)), + (GPoll::Fallback(boxed), GPoll::Final(b) | GPoll::Partial(b)) => { + let (a, e) = *boxed; + GPoll::Fallback(Box::new(((a, b), e))) + } + (GPoll::Final(a) | GPoll::Partial(a), GPoll::Fallback(boxed)) => { + let (b, e) = *boxed; + GPoll::Fallback(Box::new(((a, b), e))) + } + (GPoll::Fallback(first), GPoll::Fallback(second)) => { + let (a, e) = *first; + let (b, _) = *second; + GPoll::Fallback(Box::new(((a, b), e))) + } + (GPoll::Partial(a), GPoll::Final(b) | GPoll::Partial(b)) | (GPoll::Final(a), GPoll::Partial(b)) => GPoll::Partial((a, b)), + } + } + + #[inline(always)] + pub fn trace(self, input: usize) -> Self { + match self { + GPoll::Fallback(mut boxed) => { + boxed.1.trace.push(input); + GPoll::Fallback(boxed) + } + GPoll::Error(mut e) => { + e.trace.push(input); + GPoll::Error(e) + } + other => other, + } + } + + pub fn fallback(value: T, kind: &'static str) -> Self { + GPoll::Fallback(Box::new((value, GraphError::new(kind)))) + } + + pub fn error(kind: &'static str) -> Self { + GPoll::Error(Box::new(GraphError::new(kind))) + } + + pub fn arena_exhausted() -> Self { + GPoll::Error(Box::new(GraphError { + kind: ErrorKind::ArenaExhausted, + trace: Vec::new(), + })) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Interrupt { + Pending, + Error(Box), +} + +impl From for Interrupt { + fn from(error: GraphError) -> Self { + Interrupt::Error(Box::new(error)) + } +} + +impl From for GPoll { + fn from(interrupt: Interrupt) -> Self { + match interrupt { + Interrupt::Pending => GPoll::Pending, + Interrupt::Error(e) => GPoll::Error(e), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Extent { + Free, + Exactly(usize), +} + +impl Extent { + pub fn meet(a: GPoll, b: GPoll) -> GPoll { + a.zip(b).and_then(|(a, b)| match (a, b) { + (Extent::Free, other) | (other, Extent::Free) => GPoll::Final(other), + (Extent::Exactly(n), Extent::Exactly(m)) if n == m => GPoll::Final(Extent::Exactly(n)), + (Extent::Exactly(n), Extent::Exactly(m)) => GPoll::fallback(Extent::Exactly(n.min(m)), "extent mismatch"), + }) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Finality { + AllFinal, + Partial, +} + +impl Finality { + pub fn meet(self, other: Finality) -> Finality { + match (self, other) { + (Finality::AllFinal, Finality::AllFinal) => Finality::AllFinal, + _ => Finality::Partial, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn free_is_the_meet_identity() { + let meet = Extent::meet(GPoll::Final(Extent::Free), GPoll::Final(Extent::Exactly(4))); + assert_eq!(meet, GPoll::Final(Extent::Exactly(4))); + } + + #[test] + fn extent_mismatch_truncates_and_reports() { + let meet = Extent::meet(GPoll::Final(Extent::Exactly(3)), GPoll::Final(Extent::Exactly(5))); + let GPoll::Fallback(boxed) = meet else { + panic!("expected fallback, got {meet:?}"); + }; + assert_eq!(boxed.0, Extent::Exactly(3)); + assert!(boxed.1.kind == "extent mismatch"); + } + + #[test] + fn error_dominates_pending_in_zip() { + let zipped = GPoll::::error("boom").zip(GPoll::::Pending); + assert!(matches!(zipped, GPoll::Error(_))); + } + + #[test] + fn trace_builds_root_to_source_path() { + let poll = GPoll::::error("boom").trace(2).trace(0); + let GPoll::Error(e) = poll else { unreachable!() }; + assert_eq!(e.trace, vec![2, 0]); + } + + #[test] + fn interrupt_round_trips_to_gpoll() { + assert_eq!(GPoll::::from(Interrupt::Pending), GPoll::Pending); + let interrupt = Interrupt::from(GraphError::new("boom")); + assert!(matches!(GPoll::::from(interrupt), GPoll::Error(e) if e.kind == "boom")); + } +} diff --git a/node-graph/libraries/core-types/src/lib.rs b/node-graph/libraries/core-types/src/lib.rs index 878e7f5360..7a088d9083 100644 --- a/node-graph/libraries/core-types/src/lib.rs +++ b/node-graph/libraries/core-types/src/lib.rs @@ -4,6 +4,7 @@ pub mod bounds; pub mod consts; pub mod context; pub mod generic; +pub mod gpoll; pub mod list; pub mod math; pub mod memo; diff --git a/node-graph/libraries/core-types/src/types.rs b/node-graph/libraries/core-types/src/types.rs index 282d470ef1..646d5be1af 100644 --- a/node-graph/libraries/core-types/src/types.rs +++ b/node-graph/libraries/core-types/src/types.rs @@ -235,6 +235,7 @@ pub enum Type { Fn(Box, Box), /// Represents a future which promises to return the inner type. Future(Box), + Ref(Box), } impl Default for Type { @@ -308,6 +309,7 @@ impl Type { Self::Concrete(ty) => Some(ty.size), Self::Fn(_, _) => None, Self::Future(_) => None, + Self::Ref(_) => None, } } @@ -317,6 +319,7 @@ impl Type { Self::Concrete(ty) => Some(ty.align), Self::Fn(_, _) => None, Self::Future(_) => None, + Self::Ref(_) => None, } } @@ -326,6 +329,7 @@ impl Type { Self::Concrete(_) => self, Self::Fn(_, output) => output.nested_type(), Self::Future(output) => output.nested_type(), + Self::Ref(inner) => inner.nested_type(), } } @@ -338,6 +342,7 @@ impl Type { Self::Concrete(_) => None, Self::Fn(_, output) => output.replace_nested(f), Self::Future(output) => output.replace_nested(f), + Self::Ref(inner) => inner.replace_nested(f), } } @@ -347,6 +352,7 @@ impl Type { Type::Concrete(ty) => simplify_identifier_name(&ty.name), Type::Fn(call_arg, return_value) => format!("{} called with {}", return_value.identifier_name(), call_arg.identifier_name()), Type::Future(ty) => ty.identifier_name(), + Type::Ref(ty) => ty.identifier_name(), } } } @@ -441,6 +447,7 @@ impl std::fmt::Display for Type { Type::Concrete(ty) => write!(f, "{ty}"), Type::Fn(_, return_value) => write!(f, "{return_value}"), Type::Future(ty) => write!(f, "{ty}"), + Type::Ref(ty) => write!(f, "{ty}"), } } }