mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 07:38:11 +08:00
Add the Record wire type, record registry rows, lift and extract adapters, and executor stack reservation
This commit is contained in:
@@ -324,6 +324,7 @@ pub(crate) fn property_from_type(
|
|||||||
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context),
|
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::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),
|
Type::Ref(inner) => return property_from_type(node_id, index, inner, number_options, unit, display_decimal_places, step, context),
|
||||||
|
Type::Record(inner) => return property_from_type(node_id, index, inner, number_options, unit, display_decimal_places, step, context),
|
||||||
};
|
};
|
||||||
|
|
||||||
extra_widgets.push(widgets);
|
extra_widgets.push(widgets);
|
||||||
|
|||||||
@@ -390,6 +390,7 @@ macro_rules! tagged_value {
|
|||||||
match input {
|
match input {
|
||||||
Type::Generic(_) => None,
|
Type::Generic(_) => None,
|
||||||
Type::Ref(_) => None,
|
Type::Ref(_) => None,
|
||||||
|
Type::Record(_) => None,
|
||||||
Type::Concrete(concrete_type) => {
|
Type::Concrete(concrete_type) => {
|
||||||
let name = concrete_type.name.as_ref();
|
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
|
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
|
||||||
@@ -651,6 +652,7 @@ impl TaggedValue {
|
|||||||
match ty {
|
match ty {
|
||||||
Type::Generic(_) => None,
|
Type::Generic(_) => None,
|
||||||
Type::Ref(_) => None,
|
Type::Ref(_) => None,
|
||||||
|
Type::Record(_) => None,
|
||||||
Type::Concrete(concrete_type) => {
|
Type::Concrete(concrete_type) => {
|
||||||
let ty = concrete_type.id?;
|
let ty = concrete_type.id?;
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
|||||||
@@ -902,6 +902,8 @@ fn valid_type(from: &Type, to: &Type) -> bool {
|
|||||||
(Type::Fn(in1, out1), Type::Fn(in2, out2)) => valid_type(out2, out1) && valid_type(in1, in2),
|
(Type::Fn(in1, out1), Type::Fn(in2, out2)) => valid_type(out2, out1) && valid_type(in1, in2),
|
||||||
// A lend edge is substitutable exactly when the lent values are.
|
// A lend edge is substitutable exactly when the lent values are.
|
||||||
(Type::Ref(in1), Type::Ref(in2)) => valid_type(in1, in2),
|
(Type::Ref(in1), Type::Ref(in2)) => valid_type(in1, in2),
|
||||||
|
// A record edge is substitutable exactly when the elements are.
|
||||||
|
(Type::Record(in1), Type::Record(in2)) => valid_type(in1, in2),
|
||||||
// If either the proposed input or the allowed input are generic, we allow the substitution (meaning this is a valid subtype).
|
// If either the proposed input or the allowed input are generic, we allow the substitution (meaning this is a valid subtype).
|
||||||
// TODO: Add proper generic counting which is not based on the name
|
// TODO: Add proper generic counting which is not based on the name
|
||||||
(Type::Generic(_), _) | (_, Type::Generic(_)) => true,
|
(Type::Generic(_), _) | (_, Type::Generic(_)) => true,
|
||||||
@@ -925,6 +927,8 @@ fn ref_adapter(proposed: &Type, wanted: &Type) -> Option<ProtoNodeIdentifier> {
|
|||||||
match (proposed_output.as_ref(), wanted_output.as_ref()) {
|
match (proposed_output.as_ref(), wanted_output.as_ref()) {
|
||||||
(Type::Ref(inner), wanted_output @ Type::Concrete(_)) if valid_type(inner, wanted_output) => Some(ProtoNodeIdentifier::new("graphene_core::debug::CloneNode")),
|
(Type::Ref(inner), wanted_output @ Type::Concrete(_)) if valid_type(inner, wanted_output) => Some(ProtoNodeIdentifier::new("graphene_core::debug::CloneNode")),
|
||||||
(proposed_output @ Type::Concrete(_), Type::Ref(inner)) if valid_type(proposed_output, inner) => Some(ProtoNodeIdentifier::new("graphene_core::memo::LendNode")),
|
(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")),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -980,10 +984,23 @@ fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
|
|||||||
|
|
||||||
/// Checks if a generic type can be substituted with a concrete type and returns the concrete type
|
/// Checks if a generic type can be substituted with a concrete type and returns the concrete type
|
||||||
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
|
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
|
||||||
|
fn record_element(ty: Option<&Type>) -> Option<&Type> {
|
||||||
|
match ty {
|
||||||
|
Some(Type::Record(inner)) => Some(inner.as_ref()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
let inputs = [(Some(&types.call_argument), Some(input))]
|
let inputs = [(Some(&types.call_argument), Some(input))]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(types.inputs.iter().map(|x| x.fn_input()).zip(parameters.iter().map(|x| x.fn_input())))
|
.chain(types.inputs.iter().map(|x| x.fn_input()).zip(parameters.iter().map(|x| x.fn_input())))
|
||||||
.chain(types.inputs.iter().map(|x| x.fn_output()).zip(parameters.iter().map(|x| x.fn_output())));
|
.chain(types.inputs.iter().map(|x| x.fn_output()).zip(parameters.iter().map(|x| x.fn_output())))
|
||||||
|
.chain(
|
||||||
|
types
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|x| record_element(x.fn_output()))
|
||||||
|
.zip(parameters.iter().map(|x| record_element(x.fn_output()))),
|
||||||
|
);
|
||||||
let concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));
|
let concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Some(Type::Generic(input)) if generic == input));
|
||||||
let mut outputs = concrete_inputs.flat_map(|(_, out)| out);
|
let mut outputs = concrete_inputs.flat_map(|(_, out)| out);
|
||||||
let out_ty = outputs
|
let out_ty = outputs
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ where
|
|||||||
return Err("Output node not found in executor".into());
|
return Err("Output node not found in executor".into());
|
||||||
};
|
};
|
||||||
let mut arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner);
|
let mut arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
|
core_types::record::stack::reserve(self.tree.stack_need());
|
||||||
let result = eval_root(&mut arena, &self.runtime, &input, |ctx| match TaggedValue::from_edge(handle.duplicate(), ctx) {
|
let result = eval_root(&mut arena, &self.runtime, &input, |ctx| match TaggedValue::from_edge(handle.duplicate(), ctx) {
|
||||||
Ok(poll) => poll.map(Ok),
|
Ok(poll) => poll.map(Ok),
|
||||||
Err(error) => GPoll::Final(Err(error)),
|
Err(error) => GPoll::Final(Err(error)),
|
||||||
@@ -469,6 +470,14 @@ impl BorrowTree {
|
|||||||
pub fn source_map(&self) -> &HashMap<Path, (NodeId, NodeTypes)> {
|
pub fn source_map(&self) -> &HashMap<Path, (NodeId, NodeTypes)> {
|
||||||
&self.source_map
|
&self.source_map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The record-stack bound of an evaluation: the sum over all node frames.
|
||||||
|
pub fn stack_need(&self) -> usize {
|
||||||
|
self.nodes
|
||||||
|
.values()
|
||||||
|
.map(|(handle, _)| handle.layout().map_or(0, |layout| layout.size.next_multiple_of(8)))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -596,6 +605,40 @@ mod test {
|
|||||||
assert!(matches!(result, Some(GPoll::Final(_))), "the palette must evaluate through the spliced lend, got {result:?}");
|
assert!(matches!(result, Some(GPoll::Final(_))), "the palette must evaluate through the spliced lend, got {result:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_record_wire_types_wires_and_evaluates_through_the_registry() {
|
||||||
|
let network = ProtoNetwork {
|
||||||
|
inputs: vec![],
|
||||||
|
output: NodeId(2),
|
||||||
|
nodes: vec![
|
||||||
|
(NodeId(0), ProtoNode::value(ConstructionArgs::Value(TaggedValue::F64(7.).into()), vec![])),
|
||||||
|
(NodeId(1), proto_node("core_types::record::RecordLiftNode", vec![NodeId(0)])),
|
||||||
|
(NodeId(2), proto_node("core_types::record::RecordExtractNode", vec![NodeId(1)])),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let executor = DynamicExecutor::new(network).unwrap();
|
||||||
|
let lift = executor.tree().get(NodeId(1)).unwrap();
|
||||||
|
assert_eq!(lift.ty(), &core_types::registry::record_edge_type::<f64>());
|
||||||
|
assert!(lift.layout().is_some());
|
||||||
|
assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::F64(7.)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_lift_adapter_is_spliced_between_a_plain_producer_and_a_record_consumer() {
|
||||||
|
let network = ProtoNetwork {
|
||||||
|
inputs: vec![],
|
||||||
|
output: NodeId(1),
|
||||||
|
nodes: vec![
|
||||||
|
(NodeId(0), ProtoNode::value(ConstructionArgs::Value(TaggedValue::F64(7.).into()), vec![])),
|
||||||
|
(NodeId(1), proto_node("core_types::record::RecordExtractNode", vec![NodeId(0)])),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let executor = DynamicExecutor::new(network).unwrap();
|
||||||
|
assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::F64(7.)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_clone_out_adapter_is_spliced_between_a_lending_producer_and_an_owned_consumer() {
|
fn a_clone_out_adapter_is_spliced_between_a_lending_producer_and_an_owned_consumer() {
|
||||||
let network = ProtoNetwork {
|
let network = ProtoNetwork {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use graphene_std::transform::Footprint;
|
|||||||
use graphene_std::uuid::NodeId;
|
use graphene_std::uuid::NodeId;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::{Artboard, Context, Graphic, ProtoNodeIdentifier, SourceId, concrete, fn_type};
|
use graphene_std::{Artboard, Context, Graphic, ProtoNodeIdentifier, SourceId, concrete, fn_type};
|
||||||
use node_registry_macros::{async_node, clone_node, convert_node, frame_memo_node, into_node, lend_node};
|
use node_registry_macros::{async_node, clone_node, convert_node, frame_memo_node, into_node, lend_node, record_extract_node, record_lift_node};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
use wgpu_executor::WgpuExecutorHandle;
|
use wgpu_executor::WgpuExecutorHandle;
|
||||||
@@ -374,6 +374,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
|||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
frame_memo_node!(CanvasHandle),
|
frame_memo_node!(CanvasHandle),
|
||||||
lend_node!(f64),
|
lend_node!(f64),
|
||||||
|
record_lift_node!(f64),
|
||||||
|
record_extract_node!(f64),
|
||||||
clone_node!(f64),
|
clone_node!(f64),
|
||||||
frame_memo_node!(f64),
|
frame_memo_node!(f64),
|
||||||
lend_node!(f32),
|
lend_node!(f32),
|
||||||
@@ -769,6 +771,44 @@ mod node_registry_macros {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! record_lift_node {
|
||||||
|
($type:ty) => {
|
||||||
|
(
|
||||||
|
ProtoNodeIdentifier::new("core_types::record::RecordLiftNode"),
|
||||||
|
RegistryEntry {
|
||||||
|
io: NodeIOTypes::new(concrete!(Context), core_types::registry::record_type::<$type>(), vec![fn_type!(Context, $type)]),
|
||||||
|
constructor: |inputs| {
|
||||||
|
if inputs.len() != 1 {
|
||||||
|
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||||
|
}
|
||||||
|
let mut inputs = inputs.into_iter();
|
||||||
|
let node = core_types::record::RecordLift::<$type, _>::wire(inputs.next().unwrap().downcast::<$type>()?);
|
||||||
|
Ok(EdgeHandle::new_record::<$type>(std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! record_extract_node {
|
||||||
|
($type:ty) => {
|
||||||
|
(
|
||||||
|
ProtoNodeIdentifier::new("core_types::record::RecordExtractNode"),
|
||||||
|
RegistryEntry {
|
||||||
|
io: NodeIOTypes::new(concrete!(Context), concrete!($type), vec![core_types::registry::record_edge_type::<$type>()]),
|
||||||
|
constructor: |inputs| {
|
||||||
|
if inputs.len() != 1 {
|
||||||
|
return Err(ConstructionError::Arity { expected: 1, got: inputs.len() });
|
||||||
|
}
|
||||||
|
let mut inputs = inputs.into_iter();
|
||||||
|
let node = core_types::record::RecordExtract::<$type, _>::wire(inputs.next().unwrap().downcast_record::<$type>()?);
|
||||||
|
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$type>>))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! clone_node {
|
macro_rules! clone_node {
|
||||||
($type:ty) => {
|
($type:ty) => {
|
||||||
(
|
(
|
||||||
@@ -813,4 +853,6 @@ mod node_registry_macros {
|
|||||||
pub(crate) use frame_memo_node;
|
pub(crate) use frame_memo_node;
|
||||||
pub(crate) use into_node;
|
pub(crate) use into_node;
|
||||||
pub(crate) use lend_node;
|
pub(crate) use lend_node;
|
||||||
|
pub(crate) use record_extract_node;
|
||||||
|
pub(crate) use record_lift_node;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -352,6 +352,79 @@ impl<N> RecordSource<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lifts a plain producer onto a record wire: the element lands at offset 0
|
||||||
|
/// of a fresh element-only record. `Copy` elements only until droppable
|
||||||
|
/// elements ride records.
|
||||||
|
pub struct RecordLift<El, N> {
|
||||||
|
edge: N,
|
||||||
|
layout: Layout,
|
||||||
|
frame_bytes: usize,
|
||||||
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<El: Copy + 'static, N> RecordLift<El, N> {
|
||||||
|
pub fn wire(edge: N) -> Self {
|
||||||
|
let layout = Layout::default().with_writes(0, (size_of::<El>(), align_of::<El>()), &[]);
|
||||||
|
let frame_bytes = layout.size.next_multiple_of(8);
|
||||||
|
Self {
|
||||||
|
edge,
|
||||||
|
layout,
|
||||||
|
frame_bytes,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'e, C, El, N> Node<C> for RecordLift<El, N>
|
||||||
|
where
|
||||||
|
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||||
|
El: Copy + 'static,
|
||||||
|
N: Node<C, Output = El>,
|
||||||
|
{
|
||||||
|
type Output = RecordValue<'e>;
|
||||||
|
|
||||||
|
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
||||||
|
let dst = stack::push(self.frame_bytes);
|
||||||
|
let value = self.edge.eval(input).map(|element| {
|
||||||
|
unsafe { write_field(dst, 0, element) };
|
||||||
|
RecordValue::from_rec(unsafe { Rec::new(dst.cast_const()) })
|
||||||
|
});
|
||||||
|
stack::pop(dst);
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self) -> Option<&Layout> {
|
||||||
|
Some(&self.layout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the element from a record wire for a plain consumer.
|
||||||
|
pub struct RecordExtract<El, N> {
|
||||||
|
edge: N,
|
||||||
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<El, N> RecordExtract<El, N> {
|
||||||
|
pub fn wire(edge: N) -> Self {
|
||||||
|
Self {
|
||||||
|
edge,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'e, C, El, N> Node<C> for RecordExtract<El, N>
|
||||||
|
where
|
||||||
|
El: Copy + 'static,
|
||||||
|
N: Node<C, Output = RecordValue<'e>>,
|
||||||
|
{
|
||||||
|
type Output = El;
|
||||||
|
|
||||||
|
fn eval(&self, input: &C) -> GPoll<El> {
|
||||||
|
self.edge.eval(input).map(|value| unsafe { value.rec().element::<El>() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'e, C, N> Node<C> for RecordSource<N>
|
impl<'e, C, N> Node<C> for RecordSource<N>
|
||||||
where
|
where
|
||||||
N: Node<C, Output = RecordValue<'e>>,
|
N: Node<C, Output = RecordValue<'e>>,
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ pub type ErasedLendNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = &'c T> +
|
|||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
pub type ErasedLendNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = &'c T>;
|
pub type ErasedLendNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = &'c T>;
|
||||||
|
|
||||||
|
/// Element-independent by erasure; the wire's `Type::Record(El)` keeps element reads proven at wiring.
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
pub type ErasedRecordNode = dyn for<'c> Node<ContextImpl<'c>, Output = crate::record::RecordValue<'c>> + Send + Sync;
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
pub type ErasedRecordNode = dyn for<'c> Node<ContextImpl<'c>, Output = crate::record::RecordValue<'c>>;
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
type DynEdge = dyn std::any::Any + Send + Sync;
|
type DynEdge = dyn std::any::Any + Send + Sync;
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
@@ -96,6 +102,22 @@ pub fn lend_edge_type<T: 'static>() -> Type {
|
|||||||
Type::Fn(Box::new(concrete!(Context)), Box::new(ref_type::<T>()))
|
Type::Fn(Box::new(concrete!(Context)), Box::new(ref_type::<T>()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record_type<T: 'static>() -> Type {
|
||||||
|
Type::Record(Box::new(concrete!(T)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_edge_type<T: 'static>() -> Type {
|
||||||
|
Type::Fn(Box::new(concrete!(Context)), Box::new(record_type::<T>()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The record edge type of a token row, generic over the element.
|
||||||
|
pub fn generic_record_edge_type(name: &'static str) -> Type {
|
||||||
|
Type::Fn(
|
||||||
|
Box::new(concrete!(Context)),
|
||||||
|
Box::new(Type::Record(Box::new(Type::Generic(std::borrow::Cow::Borrowed(name))))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cache_key<C: CacheHash + ?Sized>(ctx: &C) -> u64 {
|
pub fn cache_key<C: CacheHash + ?Sized>(ctx: &C) -> u64 {
|
||||||
let mut hasher = graphene_hash::FxHasher64::new();
|
let mut hasher = graphene_hash::FxHasher64::new();
|
||||||
ctx.cache_hash(&mut hasher);
|
ctx.cache_hash(&mut hasher);
|
||||||
@@ -106,6 +128,7 @@ pub fn cache_key<C: CacheHash + ?Sized>(ctx: &C) -> u64 {
|
|||||||
pub enum ConstructionError {
|
pub enum ConstructionError {
|
||||||
Arity { expected: usize, got: usize },
|
Arity { expected: usize, got: usize },
|
||||||
Type { expected: Box<Type>, found: Box<Type> },
|
Type { expected: Box<Type>, found: Box<Type> },
|
||||||
|
MissingLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SharedEdge<N: ?Sized> {
|
pub struct SharedEdge<N: ?Sized> {
|
||||||
@@ -198,6 +221,10 @@ impl EdgeHandle {
|
|||||||
Self::new_erased(node, lend_edge_type::<T>())
|
Self::new_erased(node, lend_edge_type::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_record<T: 'static>(node: std::sync::Arc<ErasedRecordNode>) -> Self {
|
||||||
|
Self::new_erased(node, record_edge_type::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_erased<N>(node: std::sync::Arc<N>, ty: Type) -> Self
|
pub fn new_erased<N>(node: std::sync::Arc<N>, ty: Type) -> Self
|
||||||
where
|
where
|
||||||
N: ?Sized + 'static + for<'c> Node<ContextImpl<'c>>,
|
N: ?Sized + 'static + for<'c> Node<ContextImpl<'c>>,
|
||||||
@@ -242,6 +269,10 @@ impl EdgeHandle {
|
|||||||
self.downcast_erased(lend_edge_type::<T>())
|
self.downcast_erased(lend_edge_type::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn downcast_record<T: 'static>(self) -> Result<SharedEdge<ErasedRecordNode>, ConstructionError> {
|
||||||
|
self.downcast_erased(record_edge_type::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn downcast_erased<N: ?Sized + 'static>(self, expected: Type) -> Result<SharedEdge<N>, ConstructionError> {
|
pub fn downcast_erased<N: ?Sized + 'static>(self, expected: Type) -> Result<SharedEdge<N>, ConstructionError> {
|
||||||
let found = self.ty;
|
let found = self.ty;
|
||||||
self.node.downcast::<SharedEdge<N>>().map(|edge| *edge).map_err(|_| ConstructionError::Type {
|
self.node.downcast::<SharedEdge<N>>().map(|edge| *edge).map_err(|_| ConstructionError::Type {
|
||||||
|
|||||||
@@ -236,6 +236,8 @@ pub enum Type {
|
|||||||
/// Represents a future which promises to return the inner type.
|
/// Represents a future which promises to return the inner type.
|
||||||
Future(Box<Type>),
|
Future(Box<Type>),
|
||||||
Ref(Box<Type>),
|
Ref(Box<Type>),
|
||||||
|
/// A packed record wire over the element type; the layout stays node-resident metadata.
|
||||||
|
Record(Box<Type>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Type {
|
impl Default for Type {
|
||||||
@@ -310,6 +312,7 @@ impl Type {
|
|||||||
Self::Fn(_, _) => None,
|
Self::Fn(_, _) => None,
|
||||||
Self::Future(_) => None,
|
Self::Future(_) => None,
|
||||||
Self::Ref(_) => None,
|
Self::Ref(_) => None,
|
||||||
|
Self::Record(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +323,7 @@ impl Type {
|
|||||||
Self::Fn(_, _) => None,
|
Self::Fn(_, _) => None,
|
||||||
Self::Future(_) => None,
|
Self::Future(_) => None,
|
||||||
Self::Ref(_) => None,
|
Self::Ref(_) => None,
|
||||||
|
Self::Record(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,6 +334,7 @@ impl Type {
|
|||||||
Self::Fn(_, output) => output.nested_type(),
|
Self::Fn(_, output) => output.nested_type(),
|
||||||
Self::Future(output) => output.nested_type(),
|
Self::Future(output) => output.nested_type(),
|
||||||
Self::Ref(inner) => inner.nested_type(),
|
Self::Ref(inner) => inner.nested_type(),
|
||||||
|
Self::Record(inner) => inner.nested_type(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +348,7 @@ impl Type {
|
|||||||
Self::Fn(_, output) => output.replace_nested(f),
|
Self::Fn(_, output) => output.replace_nested(f),
|
||||||
Self::Future(output) => output.replace_nested(f),
|
Self::Future(output) => output.replace_nested(f),
|
||||||
Self::Ref(inner) => inner.replace_nested(f),
|
Self::Ref(inner) => inner.replace_nested(f),
|
||||||
|
Self::Record(inner) => inner.replace_nested(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +359,7 @@ impl Type {
|
|||||||
Type::Fn(call_arg, return_value) => format!("{} called with {}", return_value.identifier_name(), call_arg.identifier_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::Future(ty) => ty.identifier_name(),
|
||||||
Type::Ref(ty) => ty.identifier_name(),
|
Type::Ref(ty) => ty.identifier_name(),
|
||||||
|
Type::Record(ty) => ty.identifier_name(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,6 +455,7 @@ impl std::fmt::Display for Type {
|
|||||||
Type::Fn(_, return_value) => write!(f, "{return_value}"),
|
Type::Fn(_, return_value) => write!(f, "{return_value}"),
|
||||||
Type::Future(ty) => write!(f, "{ty}"),
|
Type::Future(ty) => write!(f, "{ty}"),
|
||||||
Type::Ref(ty) => write!(f, "{ty}"),
|
Type::Ref(ty) => write!(f, "{ty}"),
|
||||||
|
Type::Record(ty) => write!(f, "{ty}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1731,7 +1731,7 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
|||||||
return quote!();
|
return quote!();
|
||||||
}
|
}
|
||||||
if has_record_io(parsed) {
|
if has_record_io(parsed) {
|
||||||
return quote!();
|
return record_entries_tokens(parsed, struct_name, regular_fields);
|
||||||
}
|
}
|
||||||
let Some(rows) = implementation_rows(parsed, regular_fields) else {
|
let Some(rows) = implementation_rows(parsed, regular_fields) else {
|
||||||
return quote!();
|
return quote!();
|
||||||
@@ -1815,6 +1815,104 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
||||||
|
let Some(shape) = record_shape(parsed) else {
|
||||||
|
return quote!();
|
||||||
|
};
|
||||||
|
let carrier_in_fields = !shape.skips_carrier();
|
||||||
|
let values_concrete = regular_fields.iter().skip(carrier_in_fields as usize).all(|field| match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => !contains_open_generic(parsed, ty) && (lend.is_some() || !type_disqualifies(ty)),
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
if !values_concrete {
|
||||||
|
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 input_types = regular_fields.iter().enumerate().map(|(index, field)| {
|
||||||
|
let ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) = &field.ty else {
|
||||||
|
unreachable!("record nodes take no lazy inputs")
|
||||||
|
};
|
||||||
|
if carrier_in_fields && index == 0 {
|
||||||
|
return match &shape.carrier {
|
||||||
|
RecordCarrier::Token(token) => {
|
||||||
|
let name = token.to_string();
|
||||||
|
quote!(gcore::registry::generic_record_edge_type(#name))
|
||||||
|
}
|
||||||
|
RecordCarrier::Read(carrier_ty) => quote!(gcore::registry::record_edge_type::<#carrier_ty>()),
|
||||||
|
RecordCarrier::None => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match lend.is_some() {
|
||||||
|
true => quote!(gcore::registry::lend_edge_type::<#ty>()),
|
||||||
|
false => quote!(gcore::registry::edge_type::<#ty>()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let downcasts = regular_fields.iter().enumerate().map(|(index, field)| {
|
||||||
|
let name = &field.pat_ident.ident;
|
||||||
|
let ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) = &field.ty else {
|
||||||
|
unreachable!("record nodes take no lazy inputs")
|
||||||
|
};
|
||||||
|
if carrier_in_fields && index == 0 {
|
||||||
|
return quote! {
|
||||||
|
let __carrier_handle = inputs.next().unwrap();
|
||||||
|
let __carrier_ty = __carrier_handle.ty().clone();
|
||||||
|
let Some(__carrier_layout) = __carrier_handle.layout().cloned() else {
|
||||||
|
return Err(gcore::registry::ConstructionError::MissingLayout);
|
||||||
|
};
|
||||||
|
let #name = __carrier_handle.downcast_erased::<gcore::registry::ErasedRecordNode>(__carrier_ty.clone())?;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match lend.is_some() {
|
||||||
|
true => quote!(let #name = inputs.next().unwrap().downcast_lend::<#ty>()?;),
|
||||||
|
false => quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let wire_layout_arg = carrier_in_fields.then(|| quote!(&__carrier_layout,)).into_iter();
|
||||||
|
let (io_output, construct_output) = match (&shape.carrier, &shape.element_write) {
|
||||||
|
(RecordCarrier::Token(token), _) => {
|
||||||
|
let name = token.to_string();
|
||||||
|
(
|
||||||
|
quote!(gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#name))))),
|
||||||
|
quote!(Ok(gcore::registry::EdgeHandle::new_erased(
|
||||||
|
::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>,
|
||||||
|
__carrier_ty,
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(_, Some(element)) => (
|
||||||
|
quote!(gcore::registry::record_type::<#element>()),
|
||||||
|
quote!(Ok(gcore::registry::EdgeHandle::new_record::<#element>(::std::sync::Arc::new(__node)))),
|
||||||
|
),
|
||||||
|
(_, None) => unreachable!("non-token record nodes write an element"),
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
|
||||||
|
vec![gcore::registry::RegistryEntry {
|
||||||
|
io: gcore::registry::NodeIOTypes::new(
|
||||||
|
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
||||||
|
#io_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::wire(#(#names,)* #(#wire_layout_arg)*);
|
||||||
|
#construct_output
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn implementation_rows(parsed: &ParsedNodeFn, regular_fields: &[&ParsedField]) -> Option<Vec<Vec<Type>>> {
|
fn implementation_rows(parsed: &ParsedNodeFn, regular_fields: &[&ParsedField]) -> Option<Vec<Vec<Type>>> {
|
||||||
let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone());
|
let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone());
|
||||||
let open_generics: Vec<&Ident> = parsed
|
let open_generics: Vec<&Ident> = parsed
|
||||||
|
|||||||
Reference in New Issue
Block a user