mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Resolve a named read's offset when the graph compiles
The fold already holds the name and the read input's finished layout, so the offset falls out there rather than at construction: `RecordLayout` carries the resolved numbers and `set_layout` copies them into the read slots. Constructors are untouched, and census-marker reads keep their current installation. A read meets the value type the name was written at, so a disagreement between a read here and a write upstream is the same graph error as two writes disagreeing; the one-name-one-type check now spans reads and writes together. An absent attribute stays absent and the read serves the forced default rather than reporting it. `read_attribute` is the catalog's get half, typed and never `Option` at the kernel boundary, with the name declared exactly as the write side declares it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,9 @@ impl Compiler {
|
||||
proto_networks.map(move |mut proto_network| {
|
||||
proto_network.insert_context_nullification_nodes()?;
|
||||
let _ = proto_network.resolve_types(registry);
|
||||
proto_network.compute_layouts().map_err(|errors| errors.iter().map(|error| format!("{:?}", error.error)).collect::<Vec<_>>().join("\n"))?;
|
||||
proto_network
|
||||
.compute_layouts()
|
||||
.map_err(|errors| errors.iter().map(|error| format!("{:?}", error.error)).collect::<Vec<_>>().join("\n"))?;
|
||||
proto_network.generate_stable_node_ids();
|
||||
Ok(proto_network)
|
||||
})
|
||||
|
||||
@@ -380,6 +380,7 @@ impl ProtoNetwork {
|
||||
|
||||
pub fn compute_layouts(&mut self) -> Result<(), GraphErrors> {
|
||||
self.fold_attribute_names()?;
|
||||
let mut errors = GraphErrors::new();
|
||||
for index in 0..self.nodes.len() {
|
||||
let lane_invariant = self.nodes[index].1.lane_invariant_inputs;
|
||||
let layout = {
|
||||
@@ -390,6 +391,7 @@ impl ProtoNetwork {
|
||||
plan: Vec::new(),
|
||||
lane_invariant,
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
layout,
|
||||
}),
|
||||
ConstructionArgs::Nodes(inputs) => node.resolved.layout_meta.as_ref().and_then(|meta| {
|
||||
@@ -397,6 +399,10 @@ impl ProtoNetwork {
|
||||
.iter()
|
||||
.map(|input| self.nodes[input.0 as usize].1.resolved.layout.as_ref().map(|resolved| &resolved.layout))
|
||||
.collect();
|
||||
// A read meets the name's value type where the name was
|
||||
// written, so a disagreement upstream is caught here
|
||||
// rather than read as the wrong type at run time.
|
||||
errors.extend(read_type_conflicts(node, meta, &input_layouts));
|
||||
meta.sources.iter().all(|&source| input_layouts[source as usize].is_some()).then(|| core_types::record::RecordLayout {
|
||||
lane_invariant,
|
||||
..meta.resolve(&input_layouts)
|
||||
@@ -414,7 +420,7 @@ impl ProtoNetwork {
|
||||
self.nodes[index].1.resolved.layout = layout;
|
||||
}
|
||||
self.stack_need = self.fold_stack_peak();
|
||||
Ok(())
|
||||
errors.is_empty().then_some(()).ok_or(errors)
|
||||
}
|
||||
|
||||
/// Resolves every name-from-input write against the constant its name
|
||||
@@ -426,55 +432,64 @@ impl ProtoNetwork {
|
||||
let mut errors = GraphErrors::new();
|
||||
for index in 0..self.nodes.len() {
|
||||
let Some(meta) = &self.nodes[index].1.resolved.layout_meta else { continue };
|
||||
if meta.named_writes.is_empty() {
|
||||
if meta.named_writes.is_empty() && meta.named_reads.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let ConstructionArgs::Nodes(inputs) = &self.nodes[index].1.construction_args else {
|
||||
continue;
|
||||
};
|
||||
let names: Vec<Result<&'static str, GraphErrorType>> = meta
|
||||
// Writes first, then reads: both draw their names the same way, and
|
||||
// the one-name-one-type check runs over them together.
|
||||
let sources: Vec<(u8, std::any::TypeId)> = meta
|
||||
.named_writes
|
||||
.iter()
|
||||
.map(|named| match inputs.get(named.name_input as usize).map(|input| &self.nodes[input.0 as usize].1.construction_args) {
|
||||
Some(ConstructionArgs::Value(value)) => match &**value {
|
||||
value::TaggedValue::String(name) => Ok(core_types::attribute::intern_name(name)),
|
||||
other => Err(GraphErrorType::AttributeName(format!(
|
||||
"an attribute name must be text, but input {} is {}",
|
||||
named.name_input + 1,
|
||||
other.ty()
|
||||
.map(|named| (named.name_input, named.template.type_id))
|
||||
.chain(meta.named_reads.iter().map(|named| (named.name_input, named.template.type_id)))
|
||||
.collect();
|
||||
let names: Vec<Result<&'static str, GraphErrorType>> = sources
|
||||
.iter()
|
||||
.map(
|
||||
|&(name_input, _)| match inputs.get(name_input as usize).map(|input| &self.nodes[input.0 as usize].1.construction_args) {
|
||||
Some(ConstructionArgs::Value(value)) => match &**value {
|
||||
value::TaggedValue::String(name) => Ok(core_types::attribute::intern_name(name)),
|
||||
other => Err(GraphErrorType::AttributeName(format!("an attribute name must be text, but input {} is {}", name_input + 1, other.ty()))),
|
||||
},
|
||||
_ => Err(GraphErrorType::AttributeName(format!(
|
||||
"input {} must be a constant, since attribute names resolve when the graph compiles rather than when it runs",
|
||||
name_input + 1
|
||||
))),
|
||||
},
|
||||
_ => Err(GraphErrorType::AttributeName(format!(
|
||||
"input {} must be a constant, since attribute names resolve when the graph compiles rather than when it runs",
|
||||
named.name_input + 1
|
||||
))),
|
||||
})
|
||||
)
|
||||
.collect();
|
||||
let node = &self.nodes[index].1;
|
||||
let mut folded: Vec<(&'static str, std::any::TypeId)> = Vec::new();
|
||||
let mut failed = false;
|
||||
for (position, name) in names.iter().enumerate() {
|
||||
for (name, &(_, type_id)) in names.iter().zip(&sources) {
|
||||
match name {
|
||||
Err(error) => {
|
||||
errors.push(GraphError::new(node, error.clone()));
|
||||
failed = true;
|
||||
}
|
||||
Ok(name) => {
|
||||
let template = node.resolved.layout_meta.as_ref().expect("checked above").named_writes[position].template;
|
||||
if let Some(conflict) = one_name_one_type(name, template.type_id, &folded) {
|
||||
if let Some(conflict) = one_name_one_type(name, type_id, &folded) {
|
||||
errors.push(GraphError::new(node, conflict));
|
||||
failed = true;
|
||||
}
|
||||
folded.push((name, template.type_id));
|
||||
folded.push((name, type_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
continue;
|
||||
}
|
||||
let writes = self.nodes[index].1.resolved.layout_meta.as_ref().expect("checked above").named_writes.len();
|
||||
let meta = self.nodes[index].1.resolved.layout_meta.as_mut().expect("checked above");
|
||||
for (position, name) in names.into_iter().enumerate() {
|
||||
meta.fold_name(position, name.expect("every name resolved"));
|
||||
let name = name.expect("every name resolved");
|
||||
match position < writes {
|
||||
true => meta.fold_name(position, name),
|
||||
false => meta.fold_read_name(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
errors.is_empty().then_some(()).ok_or(errors)
|
||||
@@ -803,6 +818,26 @@ impl ProtoNetwork {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
/// Reports each name-from-input read whose value type disagrees with the field
|
||||
/// the name already names on the input it reads. An absent field is no
|
||||
/// conflict: the read serves the name's forced default.
|
||||
fn read_type_conflicts(node: &ProtoNode, meta: &core_types::record::LayoutMeta, inputs: &[Option<&core_types::record::Layout>]) -> GraphErrors {
|
||||
meta.named_reads
|
||||
.iter()
|
||||
.zip(&meta.folded_read_names)
|
||||
.filter_map(|(read, name)| {
|
||||
let layout = inputs.get(read.input as usize).copied().flatten()?;
|
||||
let field = layout.fields.iter().find(|field| field.name == *name && field.level == read.template.level)?;
|
||||
(field.type_id != read.template.type_id).then(|| {
|
||||
GraphError::new(
|
||||
node,
|
||||
GraphErrorType::AttributeName(format!("attribute `{name}` is read at one value type but written at another, and one name carries one value type")),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Reports a folded name that disagrees with a type the same name already
|
||||
/// carries, over the census and the names this node folded together. One name
|
||||
/// means one value type everywhere, so the layouts a graph folds can never
|
||||
|
||||
@@ -193,9 +193,7 @@ impl DynamicExecutor {
|
||||
pub fn introspect(&self, node_path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
|
||||
let result = self.tree.introspect(node_path)?;
|
||||
if result.downcast_ref::<core_types::context::CtxSnapshot>().is_some() {
|
||||
return self
|
||||
.introspect_with(node_path, graphic_types::boundary::batch_to_legacy)
|
||||
.map(Arc::from);
|
||||
return self.introspect_with(node_path, graphic_types::boundary::batch_to_legacy).map(Arc::from);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -791,6 +789,71 @@ mod test {
|
||||
assert_eq!(unsafe { layout.rec(&value).read::<&[NodeId]>(offset) }, path.as_slice());
|
||||
}
|
||||
|
||||
/// Reads `read_name` off a record that a write of `write_name` produced.
|
||||
fn read_attribute_network(write_name: &str, read_name: &str, value: TaggedValue) -> ProtoNetwork {
|
||||
ProtoNetwork {
|
||||
stack_need: 0,
|
||||
inputs: vec![],
|
||||
output: NodeId(5),
|
||||
nodes: vec![
|
||||
(NodeId(0), ProtoNode::value(ConstructionArgs::Value(TaggedValue::F64(7.).into()), vec![])),
|
||||
(NodeId(1), string_value(write_name)),
|
||||
(NodeId(2), ProtoNode::value(ConstructionArgs::Value(value.into()), vec![])),
|
||||
(NodeId(3), proto_node("graphic_nodes::graphic::WriteAttributeNode", vec![NodeId(0), NodeId(1), NodeId(2)])),
|
||||
(NodeId(4), string_value(read_name)),
|
||||
(NodeId(5), proto_node("graphic_nodes::graphic::ReadAttributeNode", vec![NodeId(3), NodeId(4)])),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn read_back(network: ProtoNetwork) -> f64 {
|
||||
let executor = build_executor(network);
|
||||
let arena = Arena::new(1 << 12).unwrap();
|
||||
let generations = [];
|
||||
let scope = EvalScope::new(None, None, None, &generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
let handle = executor.tree().get(NodeId(5)).unwrap();
|
||||
let layout = handle.layout().clone();
|
||||
let edge = handle.duplicate().downcast_record::<f64>().unwrap();
|
||||
let frames = core_types::record::test_frames(executor.tree().stack_need());
|
||||
let GPoll::Final(value) = core_types::record::serve_input(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
unsafe { core_types::record::read_element::<f64>(layout.rec(&value)) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_named_read_serves_the_written_value() {
|
||||
assert_eq!(read_back(read_attribute_network("novel:count", "novel:count", TaggedValue::F64(2.5))), 2.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_absent_named_read_serves_the_forced_default() {
|
||||
// Nothing upstream writes `novel:absent`, so the read collapses to the
|
||||
// value type's default rather than reporting absence.
|
||||
assert_eq!(read_back(read_attribute_network("novel:count", "novel:absent", TaggedValue::F64(2.5))), 0.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_census_named_read_round_trips_its_written_value() {
|
||||
// A declared name folds onto its census field, so the read resolves
|
||||
// against the same offset the write installed.
|
||||
assert_eq!(read_back(read_attribute_network("opacity", "opacity", TaggedValue::F64(0.5))), 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_named_read_disagreeing_with_its_write_is_refused() {
|
||||
// The name is written at a path upstream and read at `f64` here, which
|
||||
// is the one-name-one-type rule spanning a write and a read.
|
||||
let mut network = read_attribute_network("novel:count", "novel:count", TaggedValue::NodeIdPath(vec![NodeId(1)]));
|
||||
network.resolve_types(&node_registry::NODE_REGISTRY).unwrap();
|
||||
let errors = network.compute_layouts().expect_err("a name at two value types must be refused");
|
||||
assert!(
|
||||
errors.iter().any(|error| format!("{:?}", error.error).contains("one name carries one value type")),
|
||||
"the refusal names the one-name-one-type rule, got {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_runtime_attribute_name_is_refused_when_the_graph_compiles() {
|
||||
// The outer node's name comes off another node rather than sitting on
|
||||
|
||||
@@ -7,7 +7,7 @@ use graphene_std::raster::GPU;
|
||||
#[cfg(feature = "gpu")]
|
||||
use graphene_std::SourceId;
|
||||
use graphene_std::raster::{CPU, Raster};
|
||||
use graphene_std::registry::{ConstructionError, SourceHandle, NodeIOTypes, RegistryEntry};
|
||||
use graphene_std::registry::{ConstructionError, NodeIOTypes, RegistryEntry, SourceHandle};
|
||||
#[cfg(feature = "gpu")]
|
||||
use graphene_std::runtime::RuntimeHandle;
|
||||
|
||||
@@ -241,7 +241,9 @@ mod node_registry_macros {
|
||||
let handle = inputs.next().unwrap();
|
||||
let layout = handle.layout().clone();
|
||||
let node = graphene_std::ops::IntoNode::<$to, _, $from>::new(handle.downcast_record::<$from>()?, &layout);
|
||||
Ok(SourceHandle::new_record::<$to>(std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>))
|
||||
Ok(SourceHandle::new_record::<$to>(
|
||||
std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>
|
||||
))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -251,7 +251,12 @@ impl Arena {
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { p.cast::<T>().drop_in_place() }
|
||||
}
|
||||
self.drops.lock().unwrap().push(DropEntry { offset, type_of, drop_fn: glue::<T>, retained });
|
||||
self.drops.lock().unwrap().push(DropEntry {
|
||||
offset,
|
||||
type_of,
|
||||
drop_fn: glue::<T>,
|
||||
retained,
|
||||
});
|
||||
self.retained_heap.fetch_add(retained, Ordering::Relaxed);
|
||||
}
|
||||
// SAFETY: initialized above; insert-only, so no `&mut` to it can exist.
|
||||
@@ -772,7 +777,10 @@ mod tests {
|
||||
|
||||
let (parked, _) = transient.alloc_sized_keyed(Owner(String::from("a keyed park")), 0).unwrap();
|
||||
let src = std::ptr::from_ref(parked).cast::<u8>();
|
||||
assert!(unsafe { transient.move_park::<Twin>(src, &persistent, 0) }.is_none(), "a park of another type of the same size is refused");
|
||||
assert!(
|
||||
unsafe { transient.move_park::<Twin>(src, &persistent, 0) }.is_none(),
|
||||
"a park of another type of the same size is refused"
|
||||
);
|
||||
unsafe { transient.move_park::<Owner>(src, &persistent, 0) }.unwrap();
|
||||
assert!(unsafe { transient.move_park::<Twin>(src, &persistent, 0) }.is_none(), "the forwarding refuses the same mistype");
|
||||
|
||||
|
||||
@@ -439,7 +439,6 @@ impl AttributeDyn {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.len() == 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Clone for AttributeDyn {
|
||||
|
||||
@@ -136,7 +136,6 @@ impl<'a> RecordBatchMut<'a> {
|
||||
// SAFETY: the constructor's contract; the exclusive borrow is consumed.
|
||||
unsafe { RecordBatch::new(self.scratch.as_ptr().cast(), self.len, self.layout) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// One lane's record: its pointer paired with the batch's layout.
|
||||
|
||||
@@ -371,6 +371,12 @@ pub struct RecordLayout {
|
||||
/// leaves it empty; `set_layout` resolves its offsets through these
|
||||
/// instead of through a marker's `NAME`.
|
||||
pub named_writes: Vec<&'static str>,
|
||||
/// The offsets the fold resolved for this node's name-from-input reads, in
|
||||
/// placeholder order. `None` is an absent attribute, which the read serves
|
||||
/// as the name's forced default. Resolved in the compiler, where the folded
|
||||
/// name and the read input's finished layout sit together, so `set_layout`
|
||||
/// only copies the numbers into the read slots.
|
||||
pub named_reads: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
/// A write whose name comes from the graph rather than from a marker: the
|
||||
@@ -399,6 +405,34 @@ impl NamedWrite {
|
||||
}
|
||||
}
|
||||
|
||||
/// A read whose name comes from the graph rather than from a marker. The
|
||||
/// compiler resolves it to an offset in the read input's own layout, where
|
||||
/// the folded name and that finished layout already sit together.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NamedRead {
|
||||
/// The proto input the attribute is read from.
|
||||
pub input: u8,
|
||||
/// The proto input holding the name's constant text.
|
||||
pub name_input: u8,
|
||||
/// The read's field form, every facet but the name minted from the
|
||||
/// concrete value type, as for a write.
|
||||
pub template: FieldWrite,
|
||||
}
|
||||
|
||||
impl NamedRead {
|
||||
/// The template for a name-generic marker's read at `level`.
|
||||
pub fn of<X: 'static, V: crate::attribute::AttrValue>(input: u8, name_input: u8, level: u8) -> Self
|
||||
where
|
||||
V::Value<'static>: graphene_hash::CacheHash + PartialEq + 'static,
|
||||
{
|
||||
Self {
|
||||
input,
|
||||
name_input,
|
||||
template: FieldWrite::of::<crate::attribute::Named<X, V>>(level),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Declarative record-io metadata for a node type, emitted by the macro into
|
||||
/// its registry entry so the compiler can fold each input's layout without
|
||||
/// running the node's constructor. [`fold`](LayoutMeta::fold) reproduces the
|
||||
@@ -426,6 +460,11 @@ pub struct LayoutMeta {
|
||||
/// The names the fold gave [`named_writes`](Self::named_writes), in
|
||||
/// placeholder order. Empty until the fold runs.
|
||||
pub folded_names: Vec<&'static str>,
|
||||
/// The attributes the node reads under a name taken from the graph.
|
||||
pub named_reads: Vec<NamedRead>,
|
||||
/// The names the fold gave [`named_reads`](Self::named_reads), in
|
||||
/// placeholder order. Empty until the fold runs.
|
||||
pub folded_read_names: Vec<&'static str>,
|
||||
/// The attributes removed from the base layout, as `(name, level)`.
|
||||
pub removes: Vec<(&'static str, u8)>,
|
||||
/// The depth change the node applies: `0` for elementwise and flip nodes,
|
||||
@@ -464,6 +503,8 @@ impl LayoutMeta {
|
||||
writes: Vec::new(),
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
removes: Vec::new(),
|
||||
level_delta: 0,
|
||||
folded: None,
|
||||
@@ -508,12 +549,22 @@ impl LayoutMeta {
|
||||
// A reducer collapses its carrier's levels, so it writes a fresh record rather than copying fields down.
|
||||
_ => Vec::new(),
|
||||
};
|
||||
// A named read resolves against the input it reads, whose layout is
|
||||
// finished by the time this node folds. An absent attribute stays
|
||||
// `None`, which the read serves as the name's forced default.
|
||||
let named_reads = self
|
||||
.named_reads
|
||||
.iter()
|
||||
.zip(&self.folded_read_names)
|
||||
.map(|(read, name)| inputs.get(read.input as usize).copied().flatten().and_then(|layout| layout.offset_of(name, read.template.level)))
|
||||
.collect();
|
||||
RecordLayout {
|
||||
layout,
|
||||
frame_bytes,
|
||||
plan,
|
||||
lane_invariant: 0,
|
||||
named_writes: self.folded_names.clone(),
|
||||
named_reads,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +582,13 @@ impl LayoutMeta {
|
||||
self.writes.push(write);
|
||||
self.folded_names.push(name);
|
||||
}
|
||||
|
||||
/// Records the name of the name-from-input read at `index`. The offset
|
||||
/// itself waits for [`resolve`](Self::resolve), which is where the read
|
||||
/// input's finished layout arrives.
|
||||
pub fn fold_read_name(&mut self, name: &'static str) {
|
||||
self.folded_read_names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Field-by-field carry from `from`'s layout into `to`'s, computed at
|
||||
|
||||
@@ -25,8 +25,8 @@ pub use access::{Rec, RecordValue, apply_plan, borrow_element, erase_static, rea
|
||||
pub use frames::{FrameArena, FrameScope, Frames};
|
||||
pub use input::{DerivedLazyInput, DerivedRecordInput, ElementInput, ElementLazyInput, LevelStatus, RecordExtract, RecordInput, RecordLazyInput, fill_frames, materialize_batch, materialize_level};
|
||||
pub use layout::{
|
||||
ElToken, ElementSpec, ElementWrite, ElementWritePick, ElementWritePickHashed, ElementWritePickPlain, FieldDesc, FieldOffset, FieldWrite, InputReads, Layout, LayoutMeta, NamedWrite, RecordLayout, copy_plan,
|
||||
element_dims, element_parked, element_write, element_write_hashed, empty_layout,
|
||||
ElToken, ElementSpec, ElementWrite, ElementWritePick, ElementWritePickHashed, ElementWritePickPlain, FieldDesc, FieldOffset, FieldWrite, InputReads, Layout, LayoutMeta, NamedRead, NamedWrite,
|
||||
RecordLayout, copy_plan, element_dims, element_parked, element_write, element_write_hashed, empty_layout,
|
||||
};
|
||||
pub use owned::{OwnedRecord, deepen_field_value, has_deep_element_glue, register_deep_element_clone, register_deep_field_value, replay_field_value};
|
||||
pub use promote::{Promotion, assert_promoted, register_element_promote, register_field_promote, register_retained_heap};
|
||||
|
||||
@@ -57,10 +57,7 @@ static DEEP_FIELD_VALUES: std::sync::LazyLock<std::sync::Mutex<std::collections:
|
||||
|
||||
/// Registers the deep copy-out and replay pair for field values of `T`.
|
||||
/// Called at startup from the crate that owns the type.
|
||||
pub fn register_deep_field_value<T: 'static>(
|
||||
copy_out: fn(&dyn crate::list::AnyAttributeValue) -> Option<Box<dyn crate::list::AnyAttributeValue>>,
|
||||
replay: crate::list::FieldReplayFn,
|
||||
) {
|
||||
pub fn register_deep_field_value<T: 'static>(copy_out: fn(&dyn crate::list::AnyAttributeValue) -> Option<Box<dyn crate::list::AnyAttributeValue>>, replay: crate::list::FieldReplayFn) {
|
||||
DEEP_FIELD_VALUES.lock().unwrap().insert(std::any::TypeId::of::<T>(), DeepFieldGlue { copy_out, replay });
|
||||
}
|
||||
|
||||
|
||||
@@ -279,6 +279,7 @@ mod tests {
|
||||
layout: layout.clone(),
|
||||
lane_invariant: u32::MAX,
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
});
|
||||
RecordExtract::new(graph, &layout)
|
||||
}
|
||||
|
||||
@@ -505,12 +505,7 @@ mod run_tests {
|
||||
}
|
||||
|
||||
/// The promoted paint of one lane, at the layout the promote published.
|
||||
fn promoted_paint<'p>(
|
||||
span: &core_types::record::MaterializedSpan,
|
||||
layout: &core_types::record::Layout,
|
||||
lane: usize,
|
||||
persistent: &'p core_types::arena::Arena,
|
||||
) -> &'p List<Graphic<'p>> {
|
||||
fn promoted_paint<'p>(span: &core_types::record::MaterializedSpan, layout: &core_types::record::Layout, lane: usize, persistent: &'p core_types::arena::Arena) -> &'p List<Graphic<'p>> {
|
||||
let offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
||||
let batch = span.batch(persistent, layout).expect("the span resolves in its own region");
|
||||
// SAFETY: the promote wrote a record of `layout` into every lane.
|
||||
@@ -527,7 +522,9 @@ mod run_tests {
|
||||
// list the evaluation parked.
|
||||
let published = native_group_paint(&inner_vector, &persistent);
|
||||
let interior = {
|
||||
let Some(Graphic::Group(group)) = published.element(0) else { panic!("the paint carries a native group") };
|
||||
let Some(Graphic::Group(group)) = published.element(0) else {
|
||||
panic!("the paint carries a native group")
|
||||
};
|
||||
group.content.lanes().get(0).rec().ptr()
|
||||
};
|
||||
// SAFETY: the list serves only while `persistent` is live, and the
|
||||
@@ -538,7 +535,9 @@ mod run_tests {
|
||||
let (layout, span, _frames) = promote_paint_field(Some(paint), 1, &transient, &persistent);
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
|
||||
let Some(Graphic::Group(group)) = served.element(0) else { panic!("the promote keeps the group form") };
|
||||
let Some(Graphic::Group(group)) = served.element(0) else {
|
||||
panic!("the promote keeps the group form")
|
||||
};
|
||||
assert_eq!(group.content.lanes().get(0).rec().ptr(), interior, "a persistent interior is shared pointer for pointer");
|
||||
assert!(
|
||||
persistent.occupancy() - occupied <= layout.frame_bytes() + size_of::<List<Graphic>>() + align_of::<List<Graphic>>(),
|
||||
@@ -560,8 +559,14 @@ mod run_tests {
|
||||
|
||||
let (layout, span, _frames) = promote_paint_field(Some(paint), 2, &transient, &persistent);
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else { panic!("the promote keeps the vector") };
|
||||
assert_eq!(vector.point_domain.positions().as_ptr(), heap, "the promote moved the header, so the served paint names the pre-promote heap");
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else {
|
||||
panic!("the promote keeps the vector")
|
||||
};
|
||||
assert_eq!(
|
||||
vector.point_domain.positions().as_ptr(),
|
||||
heap,
|
||||
"the promote moved the header, so the served paint names the pre-promote heap"
|
||||
);
|
||||
assert!(std::ptr::eq(served, promoted_paint(&span, &layout, 1, &persistent)), "a paint two lanes share moves once");
|
||||
|
||||
transient.reset();
|
||||
@@ -593,7 +598,11 @@ mod run_tests {
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
let held = served.attribute::<Option<List<Graphic>>>(Stroke::NAME, 0).expect("the stroke attribute rides the promoted list");
|
||||
let held = held.as_ref().expect("the stroke is present");
|
||||
assert_eq!(map_groups_to_legacy(held.element(0).unwrap()), expected, "the attribute-held group serves from persistent storage after the reset");
|
||||
assert_eq!(
|
||||
map_groups_to_legacy(held.element(0).unwrap()),
|
||||
expected,
|
||||
"the attribute-held group serves from persistent storage after the reset"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -615,8 +624,14 @@ mod run_tests {
|
||||
|
||||
let (layout, span, _frames) = promote_paint_field(Some(paint), 1, &transient, &persistent);
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else { panic!("the promote keeps the vector") };
|
||||
assert_eq!(vector.point_domain.positions().as_ptr(), heap, "the promote moved the header, so the served paint names the pre-promote heap");
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else {
|
||||
panic!("the promote keeps the vector")
|
||||
};
|
||||
assert_eq!(
|
||||
vector.point_domain.positions().as_ptr(),
|
||||
heap,
|
||||
"the promote moved the header, so the served paint names the pre-promote heap"
|
||||
);
|
||||
|
||||
transient.reset();
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
|
||||
@@ -999,8 +999,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// more: the name is spent resolving the layout when the graph compiles, so
|
||||
// it reaches neither the kernel's parameters nor its call. The wire input
|
||||
// stays, since the fold reads the constant off it.
|
||||
let kernel_omits =
|
||||
|field: &ParsedField| injected_name(&field.pat_ident.ident) || matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { name_source: Some(_), .. }));
|
||||
let kernel_omits = |field: &ParsedField| injected_name(&field.pat_ident.ident) || matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { name_source: Some(_), .. }));
|
||||
let where_predicates: Vec<TokenStream2> = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()).map(|predicate| quote!(#predicate)).collect();
|
||||
|
||||
let NodeFields {
|
||||
@@ -1743,7 +1742,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
// kernel gets a fresh one; reference-valued writes name their real
|
||||
// lifetime explicitly and pass through untouched. An async source's value
|
||||
// outlives the evaluation, so its writes are `'static` instead.
|
||||
let attr_injected = record_io.then(|| inject_attr_lifetimes(&parsed.output_type, if async_source { "'static" } else { "'__attr" })).flatten();
|
||||
let attr_injected = record_io
|
||||
.then(|| inject_attr_lifetimes(&parsed.output_type, if async_source { "'static" } else { "'__attr" }))
|
||||
.flatten();
|
||||
let attr_lifetime = (attr_injected.is_some() && !async_source).then(|| quote!('__attr,));
|
||||
let lane_injected = gather_carrier
|
||||
.then(|| crate::codegen::classify::inject_lane_lifetime(attr_injected.as_ref().unwrap_or(&parsed.output_type)))
|
||||
@@ -2531,9 +2532,24 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
// A name-generic read's offset was resolved against the input it reads,
|
||||
// which only the compiler sees, so installing it is a copy.
|
||||
let mut folded_read = 0usize;
|
||||
let read_installs: Vec<TokenStream2> = flat_reads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (_, read))| crate::parsing::named_marker(&read.marker).is_some())
|
||||
.map(|(slot, _)| {
|
||||
let slot = format_ident!("__read_{slot}");
|
||||
let position = folded_read;
|
||||
folded_read += 1;
|
||||
quote!(self.#slot = __resolved.named_reads[#position];)
|
||||
})
|
||||
.collect();
|
||||
let plan = (!skips_carrier || gather_carrier).then(|| quote!(self.__plan = __resolved.plan;));
|
||||
Some(quote! {
|
||||
#(#write_installs)*
|
||||
#(#read_installs)*
|
||||
self.__frame_bytes = __resolved.frame_bytes;
|
||||
self.__lane_invariant = __resolved.lane_invariant;
|
||||
#plan
|
||||
@@ -2657,6 +2673,12 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let read_inits = flat_reads.iter().enumerate().map(|(slot, (owner, read))| {
|
||||
let marker = &read.marker;
|
||||
let slot = format_ident!("__read_{slot}");
|
||||
// A name-generic read has no marker name to look up here; the
|
||||
// compiler resolved its offset against the input's own layout, so
|
||||
// `set_layout` installs the number.
|
||||
if crate::parsing::named_marker(marker).is_some() {
|
||||
return quote!(let #slot = ::core::option::Option::None;);
|
||||
}
|
||||
let source = match !skips_carrier && *owner == 0 {
|
||||
true => quote!(__carrier_layout),
|
||||
false => format_ident!("__in_{owner}").to_token_stream(),
|
||||
|
||||
@@ -282,6 +282,7 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t
|
||||
});
|
||||
let writes = field_writes(&node.output.shape.attrs, core_types);
|
||||
let named_writes = named_field_writes(node, core_types, assignments);
|
||||
let named_reads = named_field_reads(node, core_types, assignments);
|
||||
let removes = node.output.removes.iter().map(|attr| {
|
||||
let marker = &attr.marker;
|
||||
let level = attr.level;
|
||||
@@ -300,6 +301,8 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t
|
||||
writes: ::std::vec![#(#writes),*],
|
||||
named_writes: ::std::vec![#(#named_writes),*],
|
||||
folded_names: ::std::vec![],
|
||||
named_reads: ::std::vec![#(#named_reads),*],
|
||||
folded_read_names: ::std::vec![],
|
||||
removes: ::std::vec![#(#removes),*],
|
||||
level_delta: #level_delta,
|
||||
folded: #folded,
|
||||
@@ -396,6 +399,28 @@ fn named_field_writes(node: &Node, core_types: &TokenStream2, assignments: &[(Id
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Emits one `NamedRead` per name-generic read, pairing the template minted
|
||||
/// from the concrete value type with the input read and the input its
|
||||
/// placeholder's name sits at.
|
||||
fn named_field_reads(node: &Node, core_types: &TokenStream2, assignments: &[(Ident, Type)]) -> Vec<TokenStream2> {
|
||||
node.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(input, source)| source.shape.attrs.iter().map(move |attr| (input, attr)))
|
||||
.filter_map(|(input, attr)| {
|
||||
let (placeholder, value) = crate::parsing::named_marker(&attr.marker)?;
|
||||
let value = qualify_projection(node, &crate::codegen::classify::substitute_ident_types(&value, assignments), assignments);
|
||||
if node.generics.iter().any(|generic| mentions_ident(&value, &generic.ident)) {
|
||||
return None;
|
||||
}
|
||||
let name = name_input(node, &placeholder)? as u8;
|
||||
let input = input as u8;
|
||||
let level = attr.level;
|
||||
Some(quote!(#core_types::record::NamedRead::of::<#placeholder, #value>(#input, #name, #level)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Rewrites `V::Assoc` into `<Row as Bound>::Assoc` once the row assigns `V`.
|
||||
/// A value type reached through an associated type needs the generic's own
|
||||
/// bound to name the projection, which only the signature carries.
|
||||
@@ -444,9 +469,7 @@ fn mentions_ident(ty: &Type, ident: &Ident) -> bool {
|
||||
/// The input position carrying `placeholder`'s name, which is the parameter
|
||||
/// declared at that placeholder.
|
||||
pub(crate) fn name_input(node: &Node, placeholder: &Type) -> Option<usize> {
|
||||
node.inputs
|
||||
.iter()
|
||||
.position(|input| input.name_source.as_ref().is_some_and(|declared| declared == placeholder))
|
||||
node.inputs.iter().position(|input| input.name_source.as_ref().is_some_and(|declared| declared == placeholder))
|
||||
}
|
||||
|
||||
fn level_delta(node: &Node) -> i8 {
|
||||
@@ -852,11 +875,7 @@ mod tests {
|
||||
let assignments = vec![(syn::parse_quote!(V), syn::parse_quote!(f64))];
|
||||
let emitted = named_field_writes(&node, "e!(gcore), &assignments);
|
||||
assert_eq!(emitted.len(), 1, "the row carries the named write, got {emitted:?}");
|
||||
assert!(
|
||||
emitted[0].to_string().contains("WireValue"),
|
||||
"the projection is qualified by the generic's bound, got {}",
|
||||
emitted[0]
|
||||
);
|
||||
assert!(emitted[0].to_string().contains("WireValue"), "the projection is qualified by the generic's bound, got {}", emitted[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1682,7 +1682,7 @@ mod tests {
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||
name_source: None,
|
||||
name_source: None,
|
||||
lend: None,
|
||||
list_levels: 0,
|
||||
ty: parse_quote!(DVec2),
|
||||
|
||||
@@ -127,7 +127,10 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
||||
_ => None,
|
||||
};
|
||||
if async_source && token.is_some() {
|
||||
emit_error!(carrier.pat_ident.span(), "an async source's element crosses the future boundary as a value; a passthrough generic element has none");
|
||||
emit_error!(
|
||||
carrier.pat_ident.span(),
|
||||
"an async source's element crosses the future boundary as a value; a passthrough generic element has none"
|
||||
);
|
||||
}
|
||||
let element = writes.as_ref().map(|writes| &writes.element).unwrap_or(&value);
|
||||
match &token {
|
||||
@@ -164,7 +167,10 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
||||
let mut seen_writes: Vec<String> = Vec::new();
|
||||
for write in &writes.markers {
|
||||
if write.owned && !async_source {
|
||||
emit_error!(parsed.output_type.span(), "an owned attribute crossing belongs to an async source; a synchronous write parks its value in the kernel");
|
||||
emit_error!(
|
||||
parsed.output_type.span(),
|
||||
"an owned attribute crossing belongs to an async source; a synchronous write parks its value in the kernel"
|
||||
);
|
||||
}
|
||||
let written = write.marker.to_token_stream().to_string();
|
||||
if seen_writes.contains(&written) {
|
||||
|
||||
@@ -663,6 +663,7 @@ mod tests {
|
||||
// pass records as lane-invariant.
|
||||
let resolved = core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
lane_invariant: u32::MAX,
|
||||
..meta.resolve(inputs)
|
||||
};
|
||||
@@ -673,6 +674,7 @@ mod tests {
|
||||
fn install_flip<N: Node<ContextImpl<'static>>>(mut node: N, layout: &Layout) -> N {
|
||||
let bundle = core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
@@ -768,6 +770,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -806,6 +810,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -852,6 +858,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -904,6 +912,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1004,6 +1014,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0, 1],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1074,6 +1086,8 @@ mod tests {
|
||||
let meta = || core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0, 1],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1150,6 +1164,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0, 1],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1196,6 +1212,8 @@ mod tests {
|
||||
let meta = || core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1207,6 +1225,8 @@ mod tests {
|
||||
let extend_meta = || core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0, 1],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1284,6 +1304,8 @@ mod tests {
|
||||
let meta = || core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1339,6 +1361,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1556,6 +1580,7 @@ mod tests {
|
||||
let mut node = MirrorNode::new(RecordSource::new(content, &layout, &layout), counting_value(true, &evals));
|
||||
let resolved = core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
lane_invariant: 0,
|
||||
..mirror_layout_meta().resolve(&[Some(&layout)])
|
||||
};
|
||||
@@ -1636,6 +1661,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1691,6 +1718,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1803,6 +1832,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
@@ -1931,6 +1962,8 @@ mod tests {
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
element: core_types::record::ElementSpec::Carried,
|
||||
|
||||
@@ -390,6 +390,22 @@ pub fn write_attribute<'e, T, V: WireValue>(
|
||||
Ok((content, Attr(parked)))
|
||||
}
|
||||
|
||||
/// Reads the attribute `name` names off each lane. An absent attribute reads as
|
||||
/// the name's own default, so the value carries the declared type either way;
|
||||
/// the name is constant text the compiler folds into an offset when the graph
|
||||
/// compiles.
|
||||
#[node_macro::node(category("Attributes: Read"))]
|
||||
pub fn read_attribute<'e>(
|
||||
_: impl Ctx,
|
||||
/// The content whose lanes carry the attribute.
|
||||
(content, value): (f64, Attr<'e, Named<Name0, f64>>),
|
||||
/// The attribute name, folded into an offset when the graph compiles.
|
||||
name: Named<Name0>,
|
||||
) -> f64 {
|
||||
let _ = content;
|
||||
*value
|
||||
}
|
||||
|
||||
/// Joins two levels of the same type, the base's lanes followed by the new's.
|
||||
#[node_macro::node(category("General"), extent(extend_extent))]
|
||||
pub fn extend<T>(
|
||||
|
||||
@@ -286,6 +286,7 @@ mod tests {
|
||||
// pass records as lane-invariant.
|
||||
let resolved = record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
lane_invariant: u32::MAX,
|
||||
..meta.resolve(inputs)
|
||||
};
|
||||
@@ -296,6 +297,7 @@ mod tests {
|
||||
fn install_flip<N: Node<ContextImpl<'static>>>(mut node: N, layout: &Layout) -> N {
|
||||
let bundle = record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
@@ -424,6 +426,8 @@ mod tests {
|
||||
record::LayoutMeta {
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
sources: vec![source],
|
||||
reads: vec![],
|
||||
element: record::ElementSpec::Carried,
|
||||
|
||||
@@ -9,13 +9,15 @@ use core_types::gpoll::GPoll;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::list::List;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::ATTR_TRANSFORM;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::math::bbox::Bbox;
|
||||
use core_types::runtime::SourceFuture;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::transform::Footprint;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use core_types::{ATTR_TRANSFORM, WasmNotSend};
|
||||
use core_types::WasmNotSend;
|
||||
use core_types::{Color, Ctx};
|
||||
pub use graph_craft::application_io::resource::{Resource, ResourceHash};
|
||||
pub use graph_craft::application_io::*;
|
||||
|
||||
@@ -273,6 +273,7 @@ mod tests {
|
||||
layout: layout.clone(),
|
||||
lane_invariant: u32::MAX,
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
},
|
||||
);
|
||||
let GPoll::Final(result) = core_types::record::serve_input(&graph, &ctx, &frames) else {
|
||||
|
||||
@@ -1602,6 +1602,7 @@ mod graphene_test {
|
||||
fn installed<N: Node<ContextImpl<'static>>>(mut node: N, layout: &Layout) -> N {
|
||||
node.set_layout(core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
@@ -1664,6 +1665,7 @@ mod graphene_test {
|
||||
let layout = out_layout::<bool>();
|
||||
wired.set_layout(core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
@@ -1711,6 +1713,7 @@ mod graphene_test {
|
||||
let layout = out_layout::<f64>();
|
||||
wired.set_layout(core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
|
||||
Reference in New Issue
Block a user