mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 20:38:12 +08:00
Turn context modification into a generic record routing node and park droppable elements as arena references
This commit is contained in:
@@ -654,6 +654,65 @@ mod test {
|
|||||||
assert!(fields.is_empty(), "an element-only record has no attribute fields");
|
assert!(fields.is_empty(), "an element-only record has no attribute fields");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn modification_value() -> ProtoNode {
|
||||||
|
let modification = core_types::ContextModification::from_sources(core_types::context::ContextFeatures::all(), &[]);
|
||||||
|
ProtoNode::value(ConstructionArgs::Value(TaggedValue::ContextModification(modification).into()), vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_context_modification_row_wires_over_a_plain_value_wire() {
|
||||||
|
let network = ProtoNetwork {
|
||||||
|
inputs: vec![],
|
||||||
|
output: NodeId(3),
|
||||||
|
nodes: vec![
|
||||||
|
(NodeId(0), ProtoNode::value(ConstructionArgs::Value(TaggedValue::F64(7.).into()), vec![])),
|
||||||
|
(NodeId(1), modification_value()),
|
||||||
|
(NodeId(2), proto_node("graphene_core::context_modification::ContextModificationNode", vec![NodeId(0), NodeId(1)])),
|
||||||
|
(NodeId(3), proto_node("core_types::record::RecordExtractNode", vec![NodeId(2)])),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let executor = DynamicExecutor::new(network).unwrap();
|
||||||
|
assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::F64(7.)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_context_modification_over_a_wire_without_a_lift_row_reports_at_typing() {
|
||||||
|
let network = ProtoNetwork {
|
||||||
|
inputs: vec![],
|
||||||
|
output: NodeId(3),
|
||||||
|
nodes: vec![
|
||||||
|
(NodeId(0), ProtoNode::value(ConstructionArgs::Value(TaggedValue::BrushStrokes(vec![]).into()), vec![])),
|
||||||
|
(NodeId(1), modification_value()),
|
||||||
|
(NodeId(2), proto_node("graphene_core::context_modification::ContextModificationNode", vec![NodeId(0), NodeId(1)])),
|
||||||
|
(NodeId(3), proto_node("core_types::record::RecordExtractNode", vec![NodeId(2)])),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = DynamicExecutor::new(network);
|
||||||
|
let error = format!("{:?}", result.err());
|
||||||
|
assert!(!error.contains("MissingLayout"), "an absent lift row must be a typing error, not a construction failure: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_context_modifications_forward_the_layout() {
|
||||||
|
let network = ProtoNetwork {
|
||||||
|
inputs: vec![],
|
||||||
|
output: NodeId(5),
|
||||||
|
nodes: vec![
|
||||||
|
(NodeId(0), ProtoNode::value(ConstructionArgs::Value(TaggedValue::F64(7.).into()), vec![])),
|
||||||
|
(NodeId(1), modification_value()),
|
||||||
|
(NodeId(2), proto_node("graphene_core::context_modification::ContextModificationNode", vec![NodeId(0), NodeId(1)])),
|
||||||
|
(NodeId(3), modification_value()),
|
||||||
|
(NodeId(4), proto_node("graphene_core::context_modification::ContextModificationNode", vec![NodeId(2), NodeId(3)])),
|
||||||
|
(NodeId(5), proto_node("core_types::record::RecordExtractNode", vec![NodeId(4)])),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let executor = DynamicExecutor::new(network).unwrap();
|
||||||
|
assert_eq!((&executor).execute(()).unwrap(), GPoll::Final(TaggedValue::F64(7.)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_lift_adapter_is_spliced_between_a_plain_producer_and_a_record_consumer() {
|
fn a_lift_adapter_is_spliced_between_a_plain_producer_and_a_record_consumer() {
|
||||||
let network = ProtoNetwork {
|
let network = ProtoNetwork {
|
||||||
|
|||||||
@@ -186,25 +186,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
|||||||
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]),
|
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]),
|
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
|
async_node!(graphene_core::memo::MonitorNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
|
||||||
// Context nullification
|
|
||||||
#[cfg(feature = "gpu")]
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc<PlatformEditorApi>, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RuntimeHandle, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => SourceId, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientSpreadMethod, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option<DAffine2>, Context => graphene_std::ContextModification]),
|
|
||||||
#[cfg(target_family = "wasm")]
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => std::sync::Arc<PlatformEditorApi>, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuExecutorHandle, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Option<wgpu_executor::WgpuExecutorHandle>, Context => graphene_std::ContextModification]),
|
|
||||||
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuPipelineCache, Context => graphene_std::ContextModification]),
|
|
||||||
// ==========
|
// ==========
|
||||||
// MEMO NODES
|
// MEMO NODES
|
||||||
// ==========
|
// ==========
|
||||||
@@ -376,6 +357,90 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
|||||||
lend_node!(f64),
|
lend_node!(f64),
|
||||||
record_lift_node!(f64),
|
record_lift_node!(f64),
|
||||||
record_extract_node!(f64),
|
record_extract_node!(f64),
|
||||||
|
record_lift_node!(()),
|
||||||
|
record_extract_node!(()),
|
||||||
|
record_lift_node!(bool),
|
||||||
|
record_extract_node!(bool),
|
||||||
|
record_lift_node!(u32),
|
||||||
|
record_extract_node!(u32),
|
||||||
|
record_lift_node!(u64),
|
||||||
|
record_extract_node!(u64),
|
||||||
|
record_lift_node!(f32),
|
||||||
|
record_extract_node!(f32),
|
||||||
|
record_lift_node!(DVec2),
|
||||||
|
record_extract_node!(DVec2),
|
||||||
|
record_lift_node!(IVec2),
|
||||||
|
record_extract_node!(IVec2),
|
||||||
|
record_lift_node!(DAffine2),
|
||||||
|
record_extract_node!(DAffine2),
|
||||||
|
record_lift_node!(Option<DAffine2>),
|
||||||
|
record_extract_node!(Option<DAffine2>),
|
||||||
|
record_lift_node!(Footprint),
|
||||||
|
record_extract_node!(Footprint),
|
||||||
|
record_lift_node!(SourceId),
|
||||||
|
record_extract_node!(SourceId),
|
||||||
|
record_lift_node!(BlendMode),
|
||||||
|
record_extract_node!(BlendMode),
|
||||||
|
record_lift_node!(graphene_std::vector::style::GradientType),
|
||||||
|
record_extract_node!(graphene_std::vector::style::GradientType),
|
||||||
|
record_lift_node!(graphene_std::vector::style::GradientSpreadMethod),
|
||||||
|
record_extract_node!(graphene_std::vector::style::GradientSpreadMethod),
|
||||||
|
record_lift_node!(ref String),
|
||||||
|
record_extract_node!(clone String),
|
||||||
|
record_lift_node!(ref List<String>),
|
||||||
|
record_extract_node!(clone List<String>),
|
||||||
|
record_lift_node!(ref List<NodeId>),
|
||||||
|
record_extract_node!(clone List<NodeId>),
|
||||||
|
record_lift_node!(ref List<f64>),
|
||||||
|
record_extract_node!(clone List<f64>),
|
||||||
|
record_lift_node!(ref List<u8>),
|
||||||
|
record_extract_node!(clone List<u8>),
|
||||||
|
record_lift_node!(ref List<Vector>),
|
||||||
|
record_extract_node!(clone List<Vector>),
|
||||||
|
record_lift_node!(ref List<Graphic>),
|
||||||
|
record_extract_node!(clone List<Graphic>),
|
||||||
|
record_lift_node!(ref List<Raster<CPU>>),
|
||||||
|
record_extract_node!(clone List<Raster<CPU>>),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_lift_node!(ref List<Raster<GPU>>),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_extract_node!(clone List<Raster<GPU>>),
|
||||||
|
record_lift_node!(ref List<Color>),
|
||||||
|
record_extract_node!(clone List<Color>),
|
||||||
|
record_lift_node!(ref List<Artboard>),
|
||||||
|
record_extract_node!(clone List<Artboard>),
|
||||||
|
record_lift_node!(ref List<GradientStops>),
|
||||||
|
record_extract_node!(clone List<GradientStops>),
|
||||||
|
record_lift_node!(ref AttributeDyn),
|
||||||
|
record_extract_node!(clone AttributeDyn),
|
||||||
|
record_lift_node!(ref AttributeValueDyn),
|
||||||
|
record_extract_node!(clone AttributeValueDyn),
|
||||||
|
record_lift_node!(ref ListDyn),
|
||||||
|
record_extract_node!(clone ListDyn),
|
||||||
|
record_lift_node!(ref std::sync::Arc<PlatformEditorApi>),
|
||||||
|
record_extract_node!(clone std::sync::Arc<PlatformEditorApi>),
|
||||||
|
record_lift_node!(ref RuntimeHandle),
|
||||||
|
record_extract_node!(clone RuntimeHandle),
|
||||||
|
record_lift_node!(ref RenderIntermediate),
|
||||||
|
record_extract_node!(clone RenderIntermediate),
|
||||||
|
record_lift_node!(ref RenderOutput),
|
||||||
|
record_extract_node!(clone RenderOutput),
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
record_lift_node!(ref CanvasHandle),
|
||||||
|
#[cfg(target_family = "wasm")]
|
||||||
|
record_extract_node!(clone CanvasHandle),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_lift_node!(ref WgpuExecutorHandle),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_extract_node!(clone WgpuExecutorHandle),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_lift_node!(ref Option<WgpuExecutorHandle>),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_extract_node!(clone Option<WgpuExecutorHandle>),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_lift_node!(ref wgpu_executor::WgpuPipelineCache),
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
record_extract_node!(clone wgpu_executor::WgpuPipelineCache),
|
||||||
(
|
(
|
||||||
ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode"),
|
ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode"),
|
||||||
RegistryEntry {
|
RegistryEntry {
|
||||||
@@ -812,6 +877,22 @@ mod node_registry_macros {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
(ref $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::RecordLiftRef::<$type, _>::new(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 {
|
macro_rules! record_extract_node {
|
||||||
@@ -833,6 +914,24 @@ mod node_registry_macros {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
(clone $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 edge = inputs.next().unwrap();
|
||||||
|
let layout = edge.layout().ok_or(ConstructionError::MissingLayout)?.clone();
|
||||||
|
let node = core_types::record::RecordExtractClone::<$type, _>::new(edge.downcast_record::<$type>()?, &layout);
|
||||||
|
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$type>>))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! clone_node {
|
macro_rules! clone_node {
|
||||||
|
|||||||
@@ -251,6 +251,64 @@ impl<'e> RecordValue<'e> {
|
|||||||
_lifetime: std::marker::PhantomData,
|
_lifetime: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rebinds the eval lifetime; validity stays stack and arena discipline,
|
||||||
|
/// which derived scopes share with their parent evaluation.
|
||||||
|
fn rebind<'a>(self) -> RecordValue<'a> {
|
||||||
|
RecordValue {
|
||||||
|
ptr: self.ptr,
|
||||||
|
_extra: self._extra,
|
||||||
|
_lifetime: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A record edge evaluable at a derived context, yielding the record at that
|
||||||
|
/// context's lifetime. The lifetime is a trait parameter because a bound like
|
||||||
|
/// `for<'d> Node<Derived<'d, C>, Output = RecordValue<'d>>` is rejected: in a
|
||||||
|
/// higher-ranked bound the lifetime must appear in a constrained input
|
||||||
|
/// position, and both the `Derived` projection and the `Output` binding are
|
||||||
|
/// unconstrained ones.
|
||||||
|
pub trait DerivedRecordEdge<'derived, C> {
|
||||||
|
fn eval_derived(&self, cell: &crate::node::StatusCell, input_index: usize, ctx: &C) -> Result<RecordValue<'derived>, crate::gpoll::Interrupt>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'derived, C, N> DerivedRecordEdge<'derived, C> for N
|
||||||
|
where
|
||||||
|
N: Node<C, Output = RecordValue<'derived>>,
|
||||||
|
{
|
||||||
|
fn eval_derived(&self, cell: &crate::node::StatusCell, input_index: usize, ctx: &C) -> Result<RecordValue<'derived>, crate::gpoll::Interrupt> {
|
||||||
|
cell.eval_input(input_index, self, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The lazy record input handed to a kernel that evaluates its edges under
|
||||||
|
/// derived contexts: evaluating rebinds the record to the kernel's routing
|
||||||
|
/// lifetime, so the value escapes the derivation scope.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RecordLazyInput<'a, 'e, N> {
|
||||||
|
node: &'a N,
|
||||||
|
cell: &'a crate::node::StatusCell,
|
||||||
|
input_index: usize,
|
||||||
|
_lifetime: std::marker::PhantomData<fn() -> RecordValue<'e>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'e, N> RecordLazyInput<'a, 'e, N> {
|
||||||
|
pub fn new(node: &'a N, cell: &'a crate::node::StatusCell, input_index: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
node,
|
||||||
|
cell,
|
||||||
|
input_index,
|
||||||
|
_lifetime: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval<'d, C>(&self, ctx: &C) -> Result<RecordValue<'e>, crate::gpoll::Interrupt>
|
||||||
|
where
|
||||||
|
N: DerivedRecordEdge<'d, C>,
|
||||||
|
{
|
||||||
|
Ok(self.node.eval_derived(self.cell, self.input_index, ctx)?.rebind())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The per-thread record stack: every record evaluation claims its activation
|
/// The per-thread record stack: every record evaluation claims its activation
|
||||||
@@ -574,6 +632,93 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lifts a droppable producer onto a record wire: the owned element parks in
|
||||||
|
/// the arena and the record's element is the parked reference, the same rule
|
||||||
|
/// reference-valued attributes follow.
|
||||||
|
pub struct RecordLiftRef<El, N> {
|
||||||
|
edge: N,
|
||||||
|
layout: Layout,
|
||||||
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<El: 'static, N> RecordLiftRef<El, N> {
|
||||||
|
pub fn new(edge: N) -> Self {
|
||||||
|
Self {
|
||||||
|
edge,
|
||||||
|
layout: Layout::default().with_writes(0, (size_of::<&El>(), align_of::<&El>()), &[]),
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'e, C, El, N> Node<C> for RecordLiftRef<El, N>
|
||||||
|
where
|
||||||
|
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena>,
|
||||||
|
El: Send + Sync + 'static,
|
||||||
|
N: Node<C, Output = El>,
|
||||||
|
{
|
||||||
|
type Output = RecordValue<'e>;
|
||||||
|
|
||||||
|
fn eval(&self, input: &C) -> GPoll<RecordValue<'e>> {
|
||||||
|
let park = |element: El| {
|
||||||
|
let (parked, _) = input.arena().alloc(element)?;
|
||||||
|
let mut value = RecordValue::zeroed();
|
||||||
|
unsafe { write_field::<&El>(value.as_mut_ptr(), 0, parked) };
|
||||||
|
Some(value)
|
||||||
|
};
|
||||||
|
let exhausted = || {
|
||||||
|
GPoll::Error(Box::new(crate::gpoll::GraphError {
|
||||||
|
kind: crate::gpoll::ErrorKind::ArenaExhausted,
|
||||||
|
trace: Vec::new(),
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
match self.edge.eval(input) {
|
||||||
|
GPoll::Final(element) => park(element).map_or_else(exhausted, GPoll::Final),
|
||||||
|
GPoll::Partial(element) => park(element).map_or_else(exhausted, GPoll::Partial),
|
||||||
|
GPoll::Fallback(boxed) => {
|
||||||
|
let (element, error) = *boxed;
|
||||||
|
park(element).map_or_else(exhausted, |value| GPoll::Fallback(Box::new((value, error))))
|
||||||
|
}
|
||||||
|
GPoll::Pending => GPoll::Pending,
|
||||||
|
GPoll::Error(error) => GPoll::Error(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self) -> Option<&Layout> {
|
||||||
|
Some(&self.layout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a parked-reference element from a record wire by cloning it out
|
||||||
|
/// for a plain consumer.
|
||||||
|
pub struct RecordExtractClone<El, N> {
|
||||||
|
edge: N,
|
||||||
|
layout: Layout,
|
||||||
|
_marker: std::marker::PhantomData<fn() -> El>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<El, N> RecordExtractClone<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 RecordExtractClone<El, N>
|
||||||
|
where
|
||||||
|
El: Clone + 'static,
|
||||||
|
N: Node<C, Output = RecordValue<'e>>,
|
||||||
|
{
|
||||||
|
type Output = El;
|
||||||
|
|
||||||
|
fn eval(&self, input: &C) -> GPoll<El> {
|
||||||
|
self.edge.eval(input).map(|value| unsafe { self.layout.rec(&value).element::<&El>() }.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Extracts the element from a record wire for a plain consumer.
|
/// Extracts the element from a record wire for a plain consumer.
|
||||||
pub struct RecordExtract<El, N> {
|
pub struct RecordExtract<El, N> {
|
||||||
edge: N,
|
edge: N,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let (data_fields, regular_fields): (Vec<_>, Vec<_>) = fields.iter().partition(|f| f.is_data_field);
|
let (data_fields, regular_fields): (Vec<_>, Vec<_>) = fields.iter().partition(|f| f.is_data_field);
|
||||||
|
|
||||||
let record = record_shape(parsed);
|
let record = record_shape(parsed);
|
||||||
|
let routing = routing_io(parsed);
|
||||||
let record_skips_carrier = record.as_ref().is_some_and(|shape| shape.skips_carrier());
|
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
|
// Record nodes with a `_: ()` primary input have no carrier edge; the unit
|
||||||
// field stays visible in the metadata but claims no struct field.
|
// field stays visible in the metadata but claims no struct field.
|
||||||
@@ -137,6 +138,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}));
|
}));
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
None if routing.is_some() => vec![quote!(pub(super) __layout: gcore::record::Layout)],
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -258,7 +260,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let all_field_inits = data_inits.chain(regular_inits).chain(slot_init);
|
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
|
// Data fields may not implement Copy, PartialEq, etc., so only derive Debug and Clone
|
||||||
let struct_derives = if record.is_some() {
|
let struct_derives = if record.is_some() || routing.is_some() {
|
||||||
quote!(#[derive(Debug, Clone)])
|
quote!(#[derive(Debug, Clone)])
|
||||||
} else if data_fields.is_empty() && !async_source {
|
} else if data_fields.is_empty() && !async_source {
|
||||||
quote!(#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)])
|
quote!(#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)])
|
||||||
@@ -285,15 +287,18 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
};
|
};
|
||||||
// Record nodes construct through the generated `wire` fn, which resolves
|
// Record nodes construct through the generated `wire` fn, which resolves
|
||||||
// offsets from the carrier layout; `new` cannot fill that state.
|
// 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 new_impl = match record.is_none() {
|
let new_impl = match record.is_none() {
|
||||||
true => quote! {
|
true => quote! {
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl<'n, #(#struct_generic_params,)*> #struct_name<#(#struct_type_params,)*>
|
impl<'n, #(#struct_generic_params,)*> #struct_name<#(#struct_type_params,)*>
|
||||||
{
|
{
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(#(#new_args,)*) -> Self {
|
pub fn new(#(#new_args,)* #(#routing_layout_param)*) -> Self {
|
||||||
Self {
|
Self {
|
||||||
#(#all_field_inits,)*
|
#(#all_field_inits,)*
|
||||||
|
#(#routing_layout_init)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -712,6 +717,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
let derive_routing = derives && routing.is_some();
|
||||||
|
|
||||||
let ctx_generic = match ctx_bounds.is_empty() {
|
let ctx_generic = match ctx_bounds.is_empty() {
|
||||||
true => quote!(#ctx_ident),
|
true => quote!(#ctx_ident),
|
||||||
@@ -721,7 +727,15 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
GenericParam::Type(type_param) if Some(&type_param.ident) == ctx_param.map(|ctx_param| &ctx_param.ident) => ctx_generic.clone(),
|
GenericParam::Type(type_param) if Some(&type_param.ident) == ctx_param.map(|ctx_param| &ctx_param.ident) => ctx_generic.clone(),
|
||||||
param => quote!(#param),
|
param => quote!(#param),
|
||||||
};
|
};
|
||||||
let mut generics: Vec<TokenStream2> = parsed.fn_generics.iter().map(&generic_tokens).collect();
|
let mut generics: Vec<TokenStream2> = parsed
|
||||||
|
.fn_generics
|
||||||
|
.iter()
|
||||||
|
.filter(|param| match param {
|
||||||
|
GenericParam::Type(type_param) => !derive_routing || Some(&type_param.ident) != routing.as_ref().map(|routing| &routing.generic),
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
.map(&generic_tokens)
|
||||||
|
.collect();
|
||||||
let mut impl_generics: Vec<TokenStream2> = parsed
|
let mut impl_generics: Vec<TokenStream2> = parsed
|
||||||
.fn_generics
|
.fn_generics
|
||||||
.iter()
|
.iter()
|
||||||
@@ -744,6 +758,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
if routing.is_some() || record.is_some() {
|
if routing.is_some() || record.is_some() {
|
||||||
impl_generics.insert(0, quote!('__record));
|
impl_generics.insert(0, quote!('__record));
|
||||||
}
|
}
|
||||||
|
if derive_routing {
|
||||||
|
generics.insert(0, quote!('__record));
|
||||||
|
}
|
||||||
|
|
||||||
let fn_name = &parsed.fn_name;
|
let fn_name = &parsed.fn_name;
|
||||||
let mod_name = format_ident!("_{}_mod", parsed.mod_name);
|
let mod_name = format_ident!("_{}_mod", parsed.mod_name);
|
||||||
@@ -760,6 +777,21 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let (data_fields, regular_fields): (Vec<_>, Vec<_>) = parsed.fields.iter().partition(|field| field.is_data_field);
|
let (data_fields, regular_fields): (Vec<_>, Vec<_>) = parsed.fields.iter().partition(|field| field.is_data_field);
|
||||||
let regular_fields: Vec<_> = regular_fields.into_iter().skip(skips_carrier as usize).collect();
|
let regular_fields: Vec<_> = regular_fields.into_iter().skip(skips_carrier as usize).collect();
|
||||||
|
|
||||||
|
if derive_routing {
|
||||||
|
for (index, field) in regular_fields.iter().enumerate() {
|
||||||
|
let source_ty = match &field.ty {
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type,
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty,
|
||||||
|
};
|
||||||
|
if matches!((&routing, source_ty), (Some(routing), Type::Path(path)) if path.path.get_ident() == Some(&routing.generic)) {
|
||||||
|
let source_generic = format_ident!("__Source{index}");
|
||||||
|
generics.push(quote! {
|
||||||
|
#source_generic: for<'__derived> #core_types::record::DerivedRecordEdge<'__derived, #core_types::context::Derived<'__derived, #ctx_ident>>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let data_field_generic_idents: Vec<Ident> = parsed
|
let data_field_generic_idents: Vec<Ident> = parsed
|
||||||
.fn_generics
|
.fn_generics
|
||||||
.iter()
|
.iter()
|
||||||
@@ -792,6 +824,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
false => quote!(#core_types::node::Node<#ctx_ident, Output = #output_type>),
|
false => quote!(#core_types::node::Node<#ctx_ident, Output = #output_type>),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let routing_source = |ty: &Type| matches!((&routing, ty), (Some(routing), Type::Path(path)) if path.path.get_ident() == Some(&routing.generic));
|
||||||
|
|
||||||
let attr_kernel_params = parsed.attribute_reads.iter().map(|read| {
|
let attr_kernel_params = parsed.attribute_reads.iter().map(|read| {
|
||||||
let pat = &read.pat_ident;
|
let pat = &read.pat_ident;
|
||||||
let marker = &read.marker;
|
let marker = &read.marker;
|
||||||
@@ -799,12 +833,17 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
});
|
});
|
||||||
let kernel_params = regular_fields
|
let kernel_params = regular_fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|field| !injected_name(&field.pat_ident.ident))
|
.enumerate()
|
||||||
.map(|field| {
|
.filter(|(_, field)| !injected_name(&field.pat_ident.ident))
|
||||||
|
.map(|(index, field)| {
|
||||||
let pat = &field.pat_ident;
|
let pat = &field.pat_ident;
|
||||||
match &field.ty {
|
match &field.ty {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if derive_routing && routing_source(output_type) => {
|
||||||
|
let source_generic = format_ident!("__Source{index}");
|
||||||
|
quote!(#pat: #core_types::record::RecordLazyInput<'_, '__record, #source_generic>)
|
||||||
|
}
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if raw_lazy => {
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if raw_lazy => {
|
||||||
let bound = lazy_bound(output_type);
|
let bound = lazy_bound(output_type);
|
||||||
quote!(#pat: &impl #bound)
|
quote!(#pat: &impl #bound)
|
||||||
@@ -817,7 +856,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
})
|
})
|
||||||
.chain(attr_kernel_params);
|
.chain(attr_kernel_params);
|
||||||
|
|
||||||
let routing_source = |ty: &Type| matches!((&routing, ty), (Some(routing), Type::Path(path)) if path.path.get_ident() == Some(&routing.generic));
|
|
||||||
let record_value_ty: Type = syn::parse_quote!(#core_types::record::RecordValue<'__record>);
|
let record_value_ty: Type = syn::parse_quote!(#core_types::record::RecordValue<'__record>);
|
||||||
let node_bounds = regular_fields.iter().enumerate().zip(&node_generics).map(|((index, field), node_generic)| match &field.ty {
|
let node_bounds = regular_fields.iter().enumerate().zip(&node_generics).map(|((index, field), node_generic)| match &field.ty {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => {
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => {
|
||||||
@@ -831,10 +869,13 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #record_value_ty>)
|
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::Regular(RegularParsedField { ty, .. }) => quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #ty>),
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if routing_source(output_type) => {
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if routing_source(output_type) => match derives {
|
||||||
let bound = lazy_bound(&record_value_ty);
|
true => quote!(#node_generic: for<'__derived> #core_types::record::DerivedRecordEdge<'__derived, #core_types::context::Derived<'__derived, #ctx_ident>>),
|
||||||
quote!(#node_generic: #bound)
|
false => {
|
||||||
}
|
let bound = lazy_bound(&record_value_ty);
|
||||||
|
quote!(#node_generic: #bound)
|
||||||
|
}
|
||||||
|
},
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => {
|
||||||
let bound = lazy_bound(output_type);
|
let bound = lazy_bound(output_type);
|
||||||
quote!(#node_generic: #bound)
|
quote!(#node_generic: #bound)
|
||||||
@@ -898,6 +939,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
Err(interrupt) => return interrupt.into(),
|
Err(interrupt) => return interrupt.into(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) if derive_routing && routing_source(output_type) => quote! {
|
||||||
|
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index);
|
||||||
|
},
|
||||||
ParsedFieldType::Node(_) if raw_lazy => quote!(),
|
ParsedFieldType::Node(_) if raw_lazy => quote!(),
|
||||||
ParsedFieldType::Node(_) => quote! {
|
ParsedFieldType::Node(_) => quote! {
|
||||||
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index);
|
||||||
@@ -994,7 +1038,14 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// lifetime explicitly and pass through untouched.
|
// lifetime explicitly and pass through untouched.
|
||||||
let kernel_output = record.as_ref().and_then(|_| inject_attr_lifetimes(&parsed.output_type));
|
let kernel_output = record.as_ref().and_then(|_| inject_attr_lifetimes(&parsed.output_type));
|
||||||
let attr_lifetime = kernel_output.is_some().then(|| quote!('__attr,));
|
let attr_lifetime = kernel_output.is_some().then(|| quote!('__attr,));
|
||||||
let kernel_output = kernel_output.map(|ty| quote!(#ty)).unwrap_or_else(|| quote!(#output_type));
|
let kernel_output = match derive_routing {
|
||||||
|
true => {
|
||||||
|
let generic = &routing.as_ref().expect("derive routing implies routing").generic;
|
||||||
|
let ty = substitute_routing_record(&parsed.output_type, generic, core_types);
|
||||||
|
quote!(#ty)
|
||||||
|
}
|
||||||
|
false => kernel_output.map(|ty| quote!(#ty)).unwrap_or_else(|| quote!(#output_type)),
|
||||||
|
};
|
||||||
let kernel = match async_fn {
|
let kernel = match async_fn {
|
||||||
false => quote! {
|
false => quote! {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -1229,16 +1280,19 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
Some(shape) if shape.skips_carrier() => {
|
Some(shape) if shape.skips_carrier() => {
|
||||||
vec![quote!(#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__record #core_types::arena::Arena>)]
|
vec![quote!(#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__record #core_types::arena::Arena>)]
|
||||||
}
|
}
|
||||||
|
None if derive_routing => {
|
||||||
|
vec![quote!(#ctx_ident: #core_types::context::ExtractArena<ArenaRef = &'__record #core_types::arena::Arena>)]
|
||||||
|
}
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let record_layout_impl = match &record {
|
let record_layout_impl = match record.is_some() || routing.is_some() {
|
||||||
Some(_) => quote! {
|
true => quote! {
|
||||||
fn layout(&self) -> Option<&#core_types::record::Layout> {
|
fn layout(&self) -> Option<&#core_types::record::Layout> {
|
||||||
Some(&self.__layout)
|
Some(&self.__layout)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => quote!(),
|
false => quote!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let entries = entries_tokens(parsed, &struct_name, &data_field_generic_idents, ®ular_fields);
|
let entries = entries_tokens(parsed, &struct_name, &data_field_generic_idents, ®ular_fields);
|
||||||
@@ -1412,6 +1466,36 @@ pub(crate) fn has_record_io(parsed: &ParsedNodeFn) -> bool {
|
|||||||
!parsed.attribute_reads.is_empty() || record_writes(&slot_value_type(&parsed.output_type)).is_some()
|
!parsed.attribute_reads.is_empty() || record_writes(&slot_value_type(&parsed.output_type)).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces the routing generic in a derive-routing kernel's return type with
|
||||||
|
/// the routing record value, since the kernel's edges rebind to '__record.
|
||||||
|
fn substitute_routing_record(output: &Type, generic: &Ident, core_types: &TokenStream2) -> Type {
|
||||||
|
struct Subst<'a> {
|
||||||
|
generic: &'a Ident,
|
||||||
|
replacement: Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitMut for Subst<'_> {
|
||||||
|
fn visit_type_mut(&mut self, ty: &mut Type) {
|
||||||
|
if let Type::Path(path) = ty
|
||||||
|
&& path.qself.is_none()
|
||||||
|
&& path.path.get_ident() == Some(self.generic)
|
||||||
|
{
|
||||||
|
*ty = self.replacement.clone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syn::visit_mut::visit_type_mut(self, ty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ty = output.clone();
|
||||||
|
let mut subst = Subst {
|
||||||
|
generic,
|
||||||
|
replacement: syn::parse_quote!(#core_types::record::RecordValue<'__record>),
|
||||||
|
};
|
||||||
|
subst.visit_type_mut(&mut ty);
|
||||||
|
ty
|
||||||
|
}
|
||||||
|
|
||||||
fn inject_attr_lifetimes(output: &Type) -> Option<Type> {
|
fn inject_attr_lifetimes(output: &Type) -> Option<Type> {
|
||||||
struct Injector {
|
struct Injector {
|
||||||
changed: bool,
|
changed: bool,
|
||||||
@@ -1734,6 +1818,9 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
|||||||
if has_record_io(parsed) {
|
if has_record_io(parsed) {
|
||||||
return record_entries_tokens(parsed, struct_name, regular_fields);
|
return record_entries_tokens(parsed, struct_name, regular_fields);
|
||||||
}
|
}
|
||||||
|
if routing_io(parsed).is_some() {
|
||||||
|
return routing_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!();
|
||||||
};
|
};
|
||||||
@@ -1816,6 +1903,113 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
fn routing_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
||||||
|
let Some(routing) = routing_io(parsed) else {
|
||||||
|
return quote!();
|
||||||
|
};
|
||||||
|
let is_source = |field: &ParsedField| {
|
||||||
|
let ty = match &field.ty {
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type,
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty,
|
||||||
|
};
|
||||||
|
matches!(ty, Type::Path(path) if path.path.get_ident() == Some(&routing.generic))
|
||||||
|
};
|
||||||
|
let values_concrete = regular_fields.iter().filter(|field| !is_source(field)).all(|field| {
|
||||||
|
let (ty, lend) = match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => (ty, lend.is_some()),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => (output_type, false),
|
||||||
|
};
|
||||||
|
!contains_open_generic(parsed, ty) && (lend || !type_disqualifies(ty))
|
||||||
|
});
|
||||||
|
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 token_name = routing.generic.to_string();
|
||||||
|
|
||||||
|
let input_types = regular_fields.iter().map(|field| {
|
||||||
|
if is_source(field) {
|
||||||
|
return quote!(gcore::registry::generic_record_edge_type(#token_name));
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(gcore::registry::lend_edge_type::<#ty>()),
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(gcore::registry::edge_type::<#ty>()),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(gcore::registry::edge_type::<#output_type>()),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let source_layouts: Vec<Ident> = regular_fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, field)| is_source(field))
|
||||||
|
.map(|(index, _)| format_ident!("__layout_{index}"))
|
||||||
|
.collect();
|
||||||
|
let downcasts = regular_fields.iter().enumerate().map(|(index, field)| {
|
||||||
|
let name = &field.pat_ident.ident;
|
||||||
|
if is_source(field) {
|
||||||
|
let layout = format_ident!("__layout_{index}");
|
||||||
|
let handle = format_ident!("__handle_{index}");
|
||||||
|
let ty = format_ident!("__ty_{index}");
|
||||||
|
return quote! {
|
||||||
|
let #handle = inputs.next().unwrap();
|
||||||
|
let #ty = #handle.ty().clone();
|
||||||
|
let Some(#layout) = #handle.layout().cloned() else {
|
||||||
|
return Err(gcore::registry::ConstructionError::MissingLayout);
|
||||||
|
};
|
||||||
|
let #name = #handle.downcast_erased::<gcore::registry::ErasedRecordNode>(#ty.clone())?;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(let #name = inputs.next().unwrap().downcast_lend::<#ty>()?;),
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#output_type>()?;),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let source_wraps = regular_fields.iter().enumerate().filter(|(_, field)| is_source(field)).map(|(index, field)| {
|
||||||
|
let name = &field.pat_ident.ident;
|
||||||
|
let layout = format_ident!("__layout_{index}");
|
||||||
|
quote!(let #name = gcore::record::RecordSource::new(#name, &#layout, &__union);)
|
||||||
|
});
|
||||||
|
let first_source_ty = regular_fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, field)| is_source(field))
|
||||||
|
.map(|(index, _)| format_ident!("__ty_{index}"))
|
||||||
|
.expect("routing nodes have a source");
|
||||||
|
|
||||||
|
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>),
|
||||||
|
gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#token_name)))),
|
||||||
|
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 __union = gcore::record::Layout::union(&[#(&#source_layouts),*]);
|
||||||
|
#(#source_wraps)*
|
||||||
|
let __node = #struct_name::new(#(#names,)* &__union);
|
||||||
|
Ok(gcore::registry::EdgeHandle::new_erased(
|
||||||
|
::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>,
|
||||||
|
#first_source_ty,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
||||||
let Some(shape) = record_shape(parsed) else {
|
let Some(shape) = record_shape(parsed) else {
|
||||||
return quote!();
|
return quote!();
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
use core::f64;
|
|
||||||
use core_types::Color;
|
|
||||||
use core_types::context::{Context, ContextModification, Ctx, DeriveCtx};
|
use core_types::context::{Context, ContextModification, Ctx, DeriveCtx};
|
||||||
use core_types::gpoll::GPoll;
|
use core_types::gpoll::Interrupt;
|
||||||
use core_types::list::{AttributeDyn, AttributeValueDyn, List, ListDyn};
|
|
||||||
use core_types::transform::Footprint;
|
|
||||||
use core_types::uuid::NodeId;
|
|
||||||
use glam::{DAffine2, DVec2};
|
|
||||||
use graphic_types::vector_types::GradientStops;
|
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
|
||||||
use raster_types::{CPU, GPU, Raster};
|
|
||||||
|
|
||||||
/// Filters out what should be unused components of the context based on the specified requirements.
|
/// Filters out what should be unused components of the context based on the specified requirements.
|
||||||
/// This node is inserted by the compiler to "zero out" unused context components.
|
/// This node is inserted by the compiler to "zero out" unused context components.
|
||||||
@@ -16,36 +7,10 @@ use raster_types::{CPU, GPU, Raster};
|
|||||||
fn context_modification<T>(
|
fn context_modification<T>(
|
||||||
ctx: impl Ctx + DeriveCtx,
|
ctx: impl Ctx + DeriveCtx,
|
||||||
/// The data to pass through, evaluated with the stripped down context.
|
/// The data to pass through, evaluated with the stripped down context.
|
||||||
#[implementations(
|
|
||||||
Context -> (),
|
|
||||||
Context -> bool,
|
|
||||||
Context -> u32,
|
|
||||||
Context -> u64,
|
|
||||||
Context -> f32,
|
|
||||||
Context -> f64,
|
|
||||||
Context -> String,
|
|
||||||
Context -> DAffine2,
|
|
||||||
Context -> Footprint,
|
|
||||||
Context -> DVec2,
|
|
||||||
Context -> List<String>,
|
|
||||||
Context -> List<NodeId>,
|
|
||||||
Context -> List<f64>,
|
|
||||||
Context -> List<u8>,
|
|
||||||
Context -> List<Vector>,
|
|
||||||
Context -> List<Graphic>,
|
|
||||||
Context -> List<Raster<CPU>>,
|
|
||||||
Context -> List<Raster<GPU>>,
|
|
||||||
Context -> List<Color>,
|
|
||||||
Context -> List<Artboard>,
|
|
||||||
Context -> List<GradientStops>,
|
|
||||||
Context -> AttributeDyn,
|
|
||||||
Context -> AttributeValueDyn,
|
|
||||||
Context -> ListDyn,
|
|
||||||
)]
|
|
||||||
value: impl Node<Context<'_>, Output = T>,
|
value: impl Node<Context<'_>, Output = T>,
|
||||||
/// The parts of the context to keep when evaluating the input value. All other parts are nullified.
|
/// The parts of the context to keep when evaluating the input value. All other parts are nullified.
|
||||||
modification: ContextModification,
|
modification: ContextModification,
|
||||||
) -> GPoll<T> {
|
) -> Result<T, Interrupt> {
|
||||||
let scope = ctx.scope().nullified(modification.features, Some(modification.sources()));
|
let scope = ctx.scope().nullified(modification.features, Some(modification.sources()));
|
||||||
value.eval(&ctx.nullified(modification.features, &scope))
|
value.eval(&ctx.nullified(modification.features, &scope))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -501,6 +501,7 @@ mod tests {
|
|||||||
ValueNode(second),
|
ValueNode(second),
|
||||||
RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
||||||
RecordSource::new(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union),
|
RecordSource::new(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union),
|
||||||
|
&union,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -537,6 +538,7 @@ mod tests {
|
|||||||
ValueNode(false),
|
ValueNode(false),
|
||||||
RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
||||||
RecordSource::new(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union),
|
RecordSource::new(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union),
|
||||||
|
&union,
|
||||||
);
|
);
|
||||||
|
|
||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
@@ -548,6 +550,102 @@ mod tests {
|
|||||||
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 0.);
|
assert_eq!(unsafe { rec.read::<f64>(union.offset_of("length", 0).unwrap()) }, 0.);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RealTimeProbe {
|
||||||
|
layout: Layout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'e> Node<ContextImpl<'e>> for RealTimeProbe {
|
||||||
|
type Output = RecordValue<'e>;
|
||||||
|
|
||||||
|
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||||
|
assert!(self.layout.is_inline());
|
||||||
|
let mut value = RecordValue::zeroed();
|
||||||
|
let element: f64 = match core_types::context::ExtractRealTime::try_real_time(input) {
|
||||||
|
Some(_) => 1.,
|
||||||
|
None => 0.,
|
||||||
|
};
|
||||||
|
unsafe { value.as_mut_ptr().cast::<f64>().write(element) };
|
||||||
|
GPoll::Final(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_modification_nullifies_for_the_inner_record_edge() {
|
||||||
|
use core_types::context::{ContextFeatures, ContextModification};
|
||||||
|
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let layout = f64_layout(&[]);
|
||||||
|
reserve_for(&[&layout]);
|
||||||
|
|
||||||
|
let probed = |features: ContextFeatures| {
|
||||||
|
let node = crate::context_modification::ContextModificationNode::new(
|
||||||
|
RealTimeProbe { layout: layout.clone() },
|
||||||
|
ValueNode(ContextModification::from_sources(features, &[])),
|
||||||
|
&layout,
|
||||||
|
);
|
||||||
|
assert_eq!(Node::<ContextImpl>::layout(&node), Some(&layout));
|
||||||
|
let GPoll::Final(value) = node.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
unsafe { layout.rec(&value).element::<f64>() }
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(probed(ContextFeatures::all()), 1., "kept features stay readable under the modification");
|
||||||
|
assert_eq!(probed(ContextFeatures::empty()), 0., "nullified features read as absent for the inner edge");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn context_modification_forwards_record_partiality() {
|
||||||
|
use core_types::context::{ContextFeatures, ContextModification};
|
||||||
|
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let layout = f64_layout(&["opacity"]);
|
||||||
|
reserve_for(&[&layout]);
|
||||||
|
|
||||||
|
let node = crate::context_modification::ContextModificationNode::new(
|
||||||
|
RecordSourceNode {
|
||||||
|
layout: layout.clone(),
|
||||||
|
element: 4.,
|
||||||
|
fields: vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)],
|
||||||
|
partial: true,
|
||||||
|
},
|
||||||
|
ValueNode(ContextModification::from_sources(ContextFeatures::all(), &[])),
|
||||||
|
&layout,
|
||||||
|
);
|
||||||
|
|
||||||
|
let GPoll::Partial(value) = node.eval(&ctx) else {
|
||||||
|
panic!("expected a partial record");
|
||||||
|
};
|
||||||
|
let rec = layout.rec(&value);
|
||||||
|
assert_eq!(unsafe { rec.element::<f64>() }, 4.);
|
||||||
|
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of("opacity", 0).unwrap()) }, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn droppable_elements_park_and_clone_out() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let lift = core_types::record::RecordLiftRef::<String, _>::new(ValueNode(String::from("parked")));
|
||||||
|
let layout = Node::<ContextImpl>::layout(&lift).unwrap().clone();
|
||||||
|
let chain = core_types::record::RecordExtractClone::<String, _>::new(lift, &layout);
|
||||||
|
|
||||||
|
let GPoll::Final(text) = chain.eval(&ctx) else {
|
||||||
|
panic!("expected a final value");
|
||||||
|
};
|
||||||
|
assert_eq!(text, "parked");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn inline_records_survive_sibling_evaluations_by_value() {
|
fn inline_records_survive_sibling_evaluations_by_value() {
|
||||||
let arena = Arena::new(1024).unwrap();
|
let arena = Arena::new(1024).unwrap();
|
||||||
@@ -565,6 +663,7 @@ mod tests {
|
|||||||
ValueNode(false),
|
ValueNode(false),
|
||||||
RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
||||||
RecordSource::new(f64_record_source(&layout_b, 3., vec![]), &layout_b, &union),
|
RecordSource::new(f64_record_source(&layout_b, 3., vec![]), &layout_b, &union),
|
||||||
|
&union,
|
||||||
);
|
);
|
||||||
|
|
||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
@@ -587,11 +686,10 @@ mod tests {
|
|||||||
let base = stack::push(0);
|
let base = stack::push(0);
|
||||||
stack::pop(base);
|
stack::pop(base);
|
||||||
|
|
||||||
let chain = ForwardRecordNode::new(RecordSource::new(
|
let chain = ForwardRecordNode::new(
|
||||||
f64_record_source(&layout, 4., vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)]),
|
RecordSource::new(f64_record_source(&layout, 4., vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)]), &layout, &layout.clone()),
|
||||||
&layout,
|
&layout,
|
||||||
&layout.clone(),
|
);
|
||||||
));
|
|
||||||
|
|
||||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a final record");
|
panic!("expected a final record");
|
||||||
@@ -612,16 +710,19 @@ mod tests {
|
|||||||
let layout = f64_layout(&["opacity"]);
|
let layout = f64_layout(&["opacity"]);
|
||||||
reserve_for(&[&layout]);
|
reserve_for(&[&layout]);
|
||||||
|
|
||||||
let chain = ForwardRecordNode::new(RecordSource::new(
|
let chain = ForwardRecordNode::new(
|
||||||
RecordSourceNode {
|
RecordSource::new(
|
||||||
layout: layout.clone(),
|
RecordSourceNode {
|
||||||
element: 4.,
|
layout: layout.clone(),
|
||||||
fields: vec![],
|
element: 4.,
|
||||||
partial: true,
|
fields: vec![],
|
||||||
},
|
partial: true,
|
||||||
|
},
|
||||||
|
&layout,
|
||||||
|
&layout.clone(),
|
||||||
|
),
|
||||||
&layout,
|
&layout,
|
||||||
&layout.clone(),
|
);
|
||||||
));
|
|
||||||
|
|
||||||
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
||||||
panic!("expected a partial record");
|
panic!("expected a partial record");
|
||||||
|
|||||||
Reference in New Issue
Block a user