mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Flip concrete value nodes onto record wires with bridge rows for every value type
This commit is contained in:
@@ -263,6 +263,27 @@ macro_rules! tagged_value {
|
||||
}
|
||||
}
|
||||
|
||||
/// The bridge rows for every wire type a value can carry, spliced
|
||||
/// while plain and record worlds coexist.
|
||||
pub fn record_bridge_entries() -> Vec<(core_types::ProtoNodeIdentifier, core_types::registry::RegistryEntry)> {
|
||||
let mut entries = Vec::new();
|
||||
entries.extend(core_types::registry::record_bridge_rows::<()>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<List<f64>>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<List<Color>>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<List<GradientStops>>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<List<BrushStroke>>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<RenderOutput>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<List<NodeId>>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<DocumentNode>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<ContextModification>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<Arc<PlatformEditorApi>>());
|
||||
entries.extend(core_types::registry::record_bridge_rows::<ResourceHash>());
|
||||
$(
|
||||
entries.extend(core_types::registry::record_bridge_rows::<$ty>());
|
||||
)*
|
||||
entries
|
||||
}
|
||||
|
||||
/// Materializes the value as [`Self::to_dynany`] does, wrapped in a `ClonedNode` edge typed by [`Self::ty`].
|
||||
pub fn to_edge(self) -> Result<EdgeHandle, String> {
|
||||
match self {
|
||||
@@ -389,8 +410,8 @@ macro_rules! tagged_value {
|
||||
pub fn from_type(input: &Type) -> Option<Self> {
|
||||
match input {
|
||||
Type::Generic(_) => None,
|
||||
Type::Ref(_) => None,
|
||||
Type::Record(_) => None,
|
||||
Type::Ref(inner) => Self::from_type(inner),
|
||||
Type::Record(inner) => Self::from_type(inner),
|
||||
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
|
||||
|
||||
@@ -934,6 +934,8 @@ fn ref_adapter(proposed: &Type, wanted: &Type) -> Option<ProtoNodeIdentifier> {
|
||||
(proposed_output @ Type::Concrete(_), Type::Ref(inner)) if valid_type(proposed_output, inner) => Some(ProtoNodeIdentifier::new("graphene_core::memo::LendNode")),
|
||||
(Type::Record(inner), wanted_output @ Type::Concrete(_)) if valid_type(inner, wanted_output) => Some(ProtoNodeIdentifier::new("core_types::record::RecordExtractNode")),
|
||||
(proposed_output @ Type::Concrete(_), Type::Record(inner)) if valid_type(proposed_output, inner) => Some(ProtoNodeIdentifier::new("core_types::record::RecordLiftNode")),
|
||||
(Type::Ref(inner), Type::Record(wanted_inner)) if valid_type(inner, wanted_inner) => Some(ProtoNodeIdentifier::new("core_types::record::RecordLiftLendNode")),
|
||||
(Type::Record(inner), Type::Ref(wanted_inner)) if valid_type(inner, wanted_inner) => Some(ProtoNodeIdentifier::new("core_types::record::RecordExtractLendNode")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,6 +677,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
.flatten(),
|
||||
);
|
||||
|
||||
node_types.extend(graph_craft::document::value::TaggedValue::record_bridge_entries());
|
||||
node_types.extend(core_types::registry::record_bridge_rows::<graphene_std::application_io::resource::Resource>());
|
||||
|
||||
let mut map: HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> = HashMap::new();
|
||||
let insert = |map: &mut HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>>, id: ProtoNodeIdentifier, entry: RegistryEntry| {
|
||||
let rows = map.entry(id).or_default();
|
||||
|
||||
@@ -693,6 +693,111 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Lifts a lending producer onto a record wire: a parked element carries the
|
||||
/// lent reference directly, a byte-carried one copies out of the borrow.
|
||||
pub struct RecordLiftLend<El, N> {
|
||||
edge: N,
|
||||
layout: Layout,
|
||||
_marker: std::marker::PhantomData<fn() -> El>,
|
||||
}
|
||||
|
||||
impl<El: 'static, N> RecordLiftLend<El, N> {
|
||||
pub fn new(edge: N) -> Self {
|
||||
Self {
|
||||
edge,
|
||||
layout: Layout::default().with_writes(0, element_dims::<El>(), &[]),
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e, C, El, N> Node<C> for RecordLiftLend<El, N>
|
||||
where
|
||||
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
El: Send + Sync + 'static,
|
||||
N: Node<C, Output = &'e El>,
|
||||
{
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
||||
let build = |element: &'e El| {
|
||||
let write = |dst: *mut u8| match element_parked::<El>() {
|
||||
true => unsafe { dst.cast::<&El>().write(element) },
|
||||
false => unsafe { std::ptr::copy_nonoverlapping((element as *const El).cast::<u8>(), dst, size_of::<El>()) },
|
||||
};
|
||||
if self.layout.is_inline() {
|
||||
let mut value = RecordValue::zeroed();
|
||||
write(value.as_mut_ptr());
|
||||
value
|
||||
} else {
|
||||
let dst = stack::push(self.layout.frame_bytes());
|
||||
write(dst);
|
||||
stack::pop(dst);
|
||||
RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })
|
||||
}
|
||||
};
|
||||
self.edge.eval(input).map(build)
|
||||
}
|
||||
|
||||
fn layout(&self) -> Option<&Layout> {
|
||||
Some(&self.layout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Lends a record wire's element: a parked element lends its arena-backed
|
||||
/// reference directly, a byte-carried one parks a copy so the borrow
|
||||
/// outlives the record.
|
||||
pub struct RecordExtractLend<El, N> {
|
||||
edge: N,
|
||||
layout: Layout,
|
||||
_marker: std::marker::PhantomData<fn() -> El>,
|
||||
}
|
||||
|
||||
impl<El, N> RecordExtractLend<El, N> {
|
||||
pub fn new(edge: N, layout: &Layout) -> Self {
|
||||
Self {
|
||||
edge,
|
||||
layout: layout.clone(),
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e, C, El, N> Node<C> for RecordExtractLend<El, N>
|
||||
where
|
||||
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||
El: Clone + Send + Sync + 'static,
|
||||
N: Node<C, Output = RecordValue<'e>>,
|
||||
{
|
||||
type Output = &'e El;
|
||||
|
||||
fn eval(&self, input: &C) -> GPoll<&'e El> {
|
||||
let exhausted = || {
|
||||
GPoll::Error(Box::new(crate::gpoll::GraphError {
|
||||
kind: crate::gpoll::ErrorKind::ArenaExhausted,
|
||||
trace: Vec::new(),
|
||||
}))
|
||||
};
|
||||
let lend = |value: RecordValue<'e>| {
|
||||
let rec = self.layout.rec(&value);
|
||||
match element_parked::<El>() {
|
||||
true => Some(unsafe { borrow_element::<El>(rec) }),
|
||||
false => input.arena().alloc(unsafe { read_element::<El>(rec) }).map(|(parked, _)| parked),
|
||||
}
|
||||
};
|
||||
match self.edge.eval(input) {
|
||||
GPoll::Final(value) => lend(value).map_or_else(exhausted, GPoll::Final),
|
||||
GPoll::Partial(value) => lend(value).map_or_else(exhausted, GPoll::Partial),
|
||||
GPoll::Fallback(boxed) => {
|
||||
let (value, error) = *boxed;
|
||||
lend(value).map_or_else(exhausted, |element| GPoll::Fallback(Box::new((element, error))))
|
||||
}
|
||||
GPoll::Pending => GPoll::Pending,
|
||||
GPoll::Error(error) => GPoll::Error(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the element from a record wire for a plain consumer, cloning out
|
||||
/// of the parked reference when the element carries drop glue.
|
||||
pub struct RecordExtract<El, N> {
|
||||
|
||||
@@ -290,6 +290,83 @@ pub struct RegistryEntry {
|
||||
pub constructor: NodeConstructor,
|
||||
}
|
||||
|
||||
/// The four bridge rows of `T`: plain and lend producers onto record wires,
|
||||
/// record wires into plain and lend consumers. One set exists per wire type
|
||||
/// while the worlds coexist.
|
||||
pub fn record_bridge_rows<T: Clone + Send + Sync + 'static>() -> [(crate::ProtoNodeIdentifier, RegistryEntry); 4] {
|
||||
[
|
||||
(crate::ProtoNodeIdentifier::new("core_types::record::RecordLiftNode"), record_lift_entry::<T>()),
|
||||
(crate::ProtoNodeIdentifier::new("core_types::record::RecordExtractNode"), record_extract_entry::<T>()),
|
||||
(crate::ProtoNodeIdentifier::new("core_types::record::RecordLiftLendNode"), record_lift_lend_entry::<T>()),
|
||||
(crate::ProtoNodeIdentifier::new("core_types::record::RecordExtractLendNode"), record_extract_lend_entry::<T>()),
|
||||
]
|
||||
}
|
||||
|
||||
/// The lift bridge row for `T`: a plain producer onto a record wire. One
|
||||
/// exists per wire type while plain and record worlds coexist.
|
||||
pub fn record_lift_entry<T: Clone + Send + Sync + 'static>() -> RegistryEntry {
|
||||
RegistryEntry {
|
||||
io: NodeIOTypes::new(concrete!(Context), record_type::<T>(), vec![edge_type::<T>()]),
|
||||
constructor: |inputs| {
|
||||
if inputs.len() != 1 {
|
||||
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||
}
|
||||
let mut inputs = inputs.into_iter();
|
||||
let node = crate::record::RecordLift::<T, _>::new(inputs.next().unwrap().downcast::<T>()?);
|
||||
Ok(EdgeHandle::new_record::<T>(std::sync::Arc::new(node) as std::sync::Arc<ErasedRecordNode>))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The extract bridge row for `T`: a record wire into a plain consumer.
|
||||
pub fn record_extract_entry<T: Clone + Send + Sync + 'static>() -> RegistryEntry {
|
||||
RegistryEntry {
|
||||
io: NodeIOTypes::new(concrete!(Context), concrete!(T), vec![record_edge_type::<T>()]),
|
||||
constructor: |inputs| {
|
||||
if inputs.len() != 1 {
|
||||
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||
}
|
||||
let mut inputs = inputs.into_iter();
|
||||
let edge = inputs.next().unwrap();
|
||||
let layout = edge.layout().ok_or(ConstructionError::MissingLayout)?.clone();
|
||||
let node = crate::record::RecordExtract::<T, _>::new(edge.downcast_record::<T>()?, &layout);
|
||||
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<T>>))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The lend-lift bridge row for `T`: a lending producer onto a record wire.
|
||||
pub fn record_lift_lend_entry<T: Clone + Send + Sync + 'static>() -> RegistryEntry {
|
||||
RegistryEntry {
|
||||
io: NodeIOTypes::new(concrete!(Context), record_type::<T>(), vec![lend_edge_type::<T>()]),
|
||||
constructor: |inputs| {
|
||||
if inputs.len() != 1 {
|
||||
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||
}
|
||||
let mut inputs = inputs.into_iter();
|
||||
let node = crate::record::RecordLiftLend::<T, _>::new(inputs.next().unwrap().downcast_lend::<T>()?);
|
||||
Ok(EdgeHandle::new_record::<T>(std::sync::Arc::new(node) as std::sync::Arc<ErasedRecordNode>))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The lend-extract bridge row for `T`: a record wire into a lend consumer.
|
||||
pub fn record_extract_lend_entry<T: Clone + Send + Sync + 'static>() -> RegistryEntry {
|
||||
RegistryEntry {
|
||||
io: NodeIOTypes::new(concrete!(Context), ref_type::<T>(), vec![record_edge_type::<T>()]),
|
||||
constructor: |inputs| {
|
||||
if inputs.len() != 1 {
|
||||
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||
}
|
||||
let mut inputs = inputs.into_iter();
|
||||
let edge = inputs.next().unwrap();
|
||||
let layout = edge.layout().ok_or(ConstructionError::MissingLayout)?.clone();
|
||||
let node = crate::record::RecordExtractLend::<T, _>::new(edge.downcast_record::<T>()?, &layout);
|
||||
Ok(EdgeHandle::new_ref(std::sync::Arc::new(node) as std::sync::Arc<ErasedLendNode<T>>))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn construct(entry: &RegistryEntry, inputs: Vec<EdgeHandle>) -> Result<EdgeHandle, ConstructionError> {
|
||||
if inputs.len() != entry.io.inputs.len() {
|
||||
return Err(ConstructionError::Arity {
|
||||
|
||||
@@ -41,6 +41,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
|
||||
let record = record_shape(parsed);
|
||||
let routing = routing_io(parsed);
|
||||
let flip = record_flip(parsed);
|
||||
let record_skips_carrier = record.as_ref().is_some_and(|shape| shape.skips_carrier());
|
||||
// Record nodes with a `_: ()` primary input have no carrier edge; the unit
|
||||
// field stays visible in the metadata but claims no struct field.
|
||||
@@ -139,6 +140,14 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
state
|
||||
}
|
||||
None if routing.is_some() => vec![quote!(pub(super) __layout: gcore::record::Layout)],
|
||||
None if flip => {
|
||||
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout), quote!(pub(super) __frame_bytes: usize)];
|
||||
state.extend((0..struct_regular_fields.len()).map(|index| {
|
||||
let slot = format_ident!("__in_{index}");
|
||||
quote!(pub(super) #slot: gcore::record::Layout)
|
||||
}));
|
||||
state
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
@@ -260,7 +269,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let all_field_inits = data_inits.chain(regular_inits).chain(slot_init);
|
||||
|
||||
// Data fields may not implement Copy, PartialEq, etc., so only derive Debug and Clone
|
||||
let struct_derives = if record.is_some() || routing.is_some() {
|
||||
let struct_derives = if record.is_some() || routing.is_some() || flip {
|
||||
quote!(#[derive(Debug, Clone)])
|
||||
} else if data_fields.is_empty() && !async_source {
|
||||
quote!(#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)])
|
||||
@@ -289,16 +298,46 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// offsets from the carrier layout; `new` cannot fill that state.
|
||||
let routing_layout_param = routing.is_some().then(|| quote!(__layout: &gcore::record::Layout,)).into_iter();
|
||||
let routing_layout_init = routing.is_some().then(|| quote!(__layout: __layout.clone(),)).into_iter();
|
||||
let flip_layout_params = flip
|
||||
.then(|| {
|
||||
(0..struct_regular_fields.len()).map(|index| {
|
||||
let slot = format_ident!("__in_{index}");
|
||||
quote!(#slot: &gcore::record::Layout,)
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.flatten();
|
||||
let flip_layout_inits = flip
|
||||
.then(|| {
|
||||
(0..struct_regular_fields.len()).map(|index| {
|
||||
let slot = format_ident!("__in_{index}");
|
||||
quote!(#slot: #slot.clone(),)
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.flatten();
|
||||
let flip_prelude = flip
|
||||
.then(|| {
|
||||
quote! {
|
||||
let __layout = gcore::record::Layout::default().with_writes(0, gcore::record::element_dims::<#slot_value_type>(), &[]);
|
||||
let __frame_bytes = __layout.frame_bytes();
|
||||
}
|
||||
})
|
||||
.into_iter();
|
||||
let flip_output_inits = flip.then(|| quote!(__layout, __frame_bytes,)).into_iter();
|
||||
let new_impl = match record.is_none() {
|
||||
true => quote! {
|
||||
#[automatically_derived]
|
||||
impl<'n, #(#struct_generic_params,)*> #struct_name<#(#struct_type_params,)*>
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(#(#new_args,)* #(#routing_layout_param)*) -> Self {
|
||||
pub fn new(#(#new_args,)* #(#routing_layout_param)* #(#flip_layout_params)*) -> Self {
|
||||
#(#flip_prelude)*
|
||||
Self {
|
||||
#(#all_field_inits,)*
|
||||
#(#routing_layout_init)*
|
||||
#(#flip_layout_inits)*
|
||||
#(#flip_output_inits)*
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -662,6 +701,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
};
|
||||
let skips_carrier = record.as_ref().is_some_and(|shape| shape.skips_carrier());
|
||||
let routing = routing_io(parsed);
|
||||
let flip = record_flip(parsed);
|
||||
let snapshot_ctx = async_fn && matches!(&parsed.input.ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "CtxSnapshot"));
|
||||
|
||||
let mut ctx_bounds: Vec<TokenStream2> = match ctx_param {
|
||||
@@ -755,7 +795,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
generics.insert(0, quote!(#lifetime));
|
||||
impl_generics.insert(0, quote!(#lifetime));
|
||||
}
|
||||
if routing.is_some() || record.is_some() {
|
||||
if routing.is_some() || record.is_some() || flip {
|
||||
impl_generics.insert(0, quote!('__record));
|
||||
}
|
||||
if derive_routing {
|
||||
@@ -768,6 +808,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let output_type = &parsed.output_type;
|
||||
let trait_output = match (&record, &routing) {
|
||||
(Some(_), _) | (None, Some(_)) => syn::parse_quote!(#core_types::record::RecordValue<'__record>),
|
||||
(None, None) if flip => syn::parse_quote!(#core_types::record::RecordValue<'__record>),
|
||||
(None, None) => slot_value_type(&parsed.output_type),
|
||||
};
|
||||
let raw_lazy = matches!(kernel_kind(&parsed.output_type), KernelKind::Poll(_));
|
||||
@@ -868,6 +909,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if routing_source(ty) => {
|
||||
quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>)
|
||||
}
|
||||
ParsedFieldType::Regular(_) if flip => quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>),
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #ty>),
|
||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if routing_source(output_type) => match derives {
|
||||
true => quote!(#node_generic: for<'__derived> #core_types::record::DerivedRecordEdge<'__derived, #core_types::context::Derived<'__derived, #ctx_ident>>),
|
||||
@@ -933,6 +975,16 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let name = &field.pat_ident.ident;
|
||||
match &field.ty {
|
||||
ParsedFieldType::Regular(_) if record.is_some() && !skips_carrier && index == 0 => quote!(),
|
||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if flip => {
|
||||
let slot = format_ident!("__in_{index}");
|
||||
quote! {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => return interrupt.into(),
|
||||
};
|
||||
let #name: #ty = unsafe { #core_types::record::read_element(self.#slot.rec(&#name)) };
|
||||
}
|
||||
}
|
||||
ParsedFieldType::Regular(_) => quote! {
|
||||
let #name = match __cell.eval_input(#index, &self.#name, __input) {
|
||||
Ok(value) => value,
|
||||
@@ -1223,10 +1275,41 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
__cell.finish(__value)
|
||||
}
|
||||
});
|
||||
let flip_tail = flip.then(|| {
|
||||
let kernel_value = match kernel_kind(&parsed.output_type) {
|
||||
KernelKind::Interrupt(_) => quote! {
|
||||
match #kernel_call {
|
||||
Ok(value) => value,
|
||||
Err(interrupt) => return interrupt.into(),
|
||||
}
|
||||
},
|
||||
_ => quote!(#kernel_call),
|
||||
};
|
||||
quote! {
|
||||
let __kernel_value = #kernel_value;
|
||||
let mut __value = #core_types::record::RecordValue::zeroed();
|
||||
let __dst = match self.__frame_bytes {
|
||||
0 => __value.as_mut_ptr(),
|
||||
__bytes => #core_types::record::stack::push(__bytes),
|
||||
};
|
||||
let __written = unsafe { #core_types::record::write_element(__dst, __kernel_value, #core_types::context::ExtractArena::arena(__input)) };
|
||||
if self.__frame_bytes != 0 {
|
||||
#core_types::record::stack::pop(__dst);
|
||||
__value = #core_types::record::RecordValue::spilled(unsafe { #core_types::record::Rec::new(__dst.cast_const()) });
|
||||
}
|
||||
match __written {
|
||||
Some(()) => __cell.finish(__value),
|
||||
None => #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#core_types::gpoll::GraphError {
|
||||
kind: #core_types::gpoll::ErrorKind::ArenaExhausted,
|
||||
trace: ::std::vec::Vec::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
});
|
||||
let eval_tail = match (async_fn, future_kernel) {
|
||||
(false, false) => match record_tail {
|
||||
Some(tail) => tail,
|
||||
None => lift,
|
||||
None => flip_tail.unwrap_or(lift),
|
||||
},
|
||||
(true, _) => {
|
||||
let kernel_value_names: Vec<&Ident> = kernel_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
||||
@@ -1280,13 +1363,13 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
Some(shape) if shape.skips_carrier() => {
|
||||
vec![quote!(#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__record #core_types::arena::Arena>)]
|
||||
}
|
||||
None if derive_routing => {
|
||||
None if derive_routing || flip => {
|
||||
vec![quote!(#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__record #core_types::arena::Arena>)]
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
let record_layout_impl = match record.is_some() || routing.is_some() {
|
||||
let record_layout_impl = match record.is_some() || routing.is_some() || flip {
|
||||
true => quote! {
|
||||
fn layout(&self) -> Option<&#core_types::record::Layout> {
|
||||
Some(&self.__layout)
|
||||
@@ -1615,6 +1698,41 @@ pub(crate) struct RoutingIo {
|
||||
pub(crate) generic: Ident,
|
||||
}
|
||||
|
||||
/// Whether a plain node's lowering flips onto record wires: sync,
|
||||
/// fully-concrete value-input nodes in this cut; batch, shader, async, lend,
|
||||
/// lazy, and generic nodes keep the plain lowering until their record forms
|
||||
/// land.
|
||||
pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool {
|
||||
if record_shape(parsed).is_some() || has_record_io(parsed) || routing_io(parsed).is_some() {
|
||||
return false;
|
||||
}
|
||||
if parsed.is_async || is_source_kernel(&parsed.output_type) {
|
||||
return false;
|
||||
}
|
||||
if parsed.attributes.batch.is_some() || parsed.attributes.shader_node.is_some() {
|
||||
return false;
|
||||
}
|
||||
if matches!(kernel_kind(&parsed.output_type), KernelKind::Poll(_)) {
|
||||
return false;
|
||||
}
|
||||
if matches!(slot_value_type(&parsed.output_type), Type::Reference(_)) {
|
||||
return false;
|
||||
}
|
||||
let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone());
|
||||
let concrete = parsed.fn_generics.iter().all(|param| match param {
|
||||
GenericParam::Type(type_param) => Some(&type_param.ident) == ctx_ident.as_ref(),
|
||||
GenericParam::Lifetime(_) => false,
|
||||
GenericParam::Const(_) => false,
|
||||
});
|
||||
if !concrete {
|
||||
return false;
|
||||
}
|
||||
parsed.fields.iter().all(|field| match &field.ty {
|
||||
ParsedFieldType::Regular(RegularParsedField { lend, .. }) => lend.is_none(),
|
||||
ParsedFieldType::Node(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn routing_io(parsed: &ParsedNodeFn) -> Option<RoutingIo> {
|
||||
if has_record_io(parsed) || parsed.is_async {
|
||||
return None;
|
||||
@@ -1821,6 +1939,9 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
||||
if routing_io(parsed).is_some() {
|
||||
return routing_entries_tokens(parsed, struct_name, regular_fields);
|
||||
}
|
||||
if record_flip(parsed) {
|
||||
return flip_entries_tokens(parsed, struct_name, regular_fields);
|
||||
}
|
||||
let Some(rows) = implementation_rows(parsed, regular_fields) else {
|
||||
return quote!();
|
||||
};
|
||||
@@ -1903,6 +2024,71 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
||||
}
|
||||
}
|
||||
|
||||
/// The registry rows of a flipped plain node: every wire is a record wire,
|
||||
/// inputs resolve their layouts off the claimed handles, and the output is an
|
||||
/// element-only record of the kernel's return type.
|
||||
fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
||||
let Some(rows) = implementation_rows(parsed, regular_fields) else {
|
||||
return quote!();
|
||||
};
|
||||
let rows: Vec<&Vec<Type>> = rows.iter().filter(|row| row.iter().all(|ty| !type_disqualifies(ty))).collect();
|
||||
if rows.is_empty() {
|
||||
return quote!();
|
||||
}
|
||||
let output = slot_value_type(&parsed.output_type);
|
||||
if type_disqualifies(&output) {
|
||||
return quote!();
|
||||
}
|
||||
|
||||
let fn_name = &parsed.fn_name;
|
||||
let entries_name = format_ident!("{}_entries", fn_name);
|
||||
let arity = regular_fields.len();
|
||||
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
||||
|
||||
let entries = rows.iter().map(|row| {
|
||||
let input_types = row.iter().map(|ty| quote!(gcore::registry::record_edge_type::<#ty>()));
|
||||
let downcasts = names.iter().zip(row.iter()).enumerate().map(|(index, (name, ty))| {
|
||||
let handle = format_ident!("__handle_{index}");
|
||||
let layout = format_ident!("__layout_{index}");
|
||||
quote! {
|
||||
let #handle = inputs.next().unwrap();
|
||||
let Some(#layout) = #handle.layout().cloned() else {
|
||||
return Err(gcore::registry::ConstructionError::MissingLayout);
|
||||
};
|
||||
let #name = #handle.downcast_record::<#ty>()?;
|
||||
}
|
||||
});
|
||||
let layout_args = (0..arity).map(|index| {
|
||||
let layout = format_ident!("__layout_{index}");
|
||||
quote!(&#layout,)
|
||||
});
|
||||
quote! {
|
||||
gcore::registry::RegistryEntry {
|
||||
io: gcore::registry::NodeIOTypes::new(
|
||||
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
||||
gcore::registry::record_type::<#output>(),
|
||||
vec![#(#input_types),*],
|
||||
),
|
||||
constructor: |inputs| {
|
||||
if inputs.len() != #arity {
|
||||
return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() });
|
||||
}
|
||||
let mut inputs = inputs.into_iter();
|
||||
#(#downcasts)*
|
||||
let __node = #struct_name::new(#(#names,)* #(#layout_args)*);
|
||||
Ok(gcore::registry::EdgeHandle::new_record::<#output>(::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>))
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
|
||||
vec![#(#entries),*]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The registry row of a routing node: one instance covers every element,
|
||||
/// sources claim generic record edges, and the constructor wraps each source
|
||||
/// in its union translation and stores the union as the node's layout.
|
||||
|
||||
Reference in New Issue
Block a user