Implement dynamic table attributes to generalize the graphic-specific Table type (#4050)

* Feature-gate serde derives behind cfg_attr in all runtime node graph type crates

* Refactor Table to move its hard-coded fields into an attributes field

* Encapsulate TableRow/TableRowRef/TableRowMut attribute fields behind accessor methods

* Remove TaggedValue::GraphicUnused

* Refactor Table<T> to use dynamic attributes instead fixed names

* Fix code review soundness concerns

* Add todo work

* Replace row-oriented Table<T> API with column-oriented access

* Fix attribute propagation bugs

---------
This commit is contained in:
Keavon Chambers
2026-04-28 02:10:24 -07:00
parent 324b9e664c
commit 76938eb69a
75 changed files with 2330 additions and 1349 deletions

View File

@@ -1,7 +1,7 @@
use crate::gcore::Context;
use core::f64::consts::TAU;
use core_types::registry::types::{Angle, PixelSize};
use core_types::table::{Table, TableRowRef};
use core_types::table::Table;
use core_types::{CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use graphic_types::{Graphic, Vector};
@@ -77,12 +77,13 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
let generated_instance = instance.eval(new_ctx.into_context()).await;
for row in generated_instance.iter() {
let mut row = row.into_cloned();
for row_index in 0..generated_instance.len() {
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
let local_translation = DAffine2::from_translation(row.transform.translation);
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
row.transform = local_translation * transform * local_matrix;
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let local_translation = DAffine2::from_translation(local_transform.translation);
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
*row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix;
result_table.push(row);
}
@@ -122,12 +123,13 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
let generated_instance = instance.eval(new_ctx.into_context()).await;
for row in generated_instance.iter() {
let mut row = row.into_cloned();
for row_index in 0..generated_instance.len() {
let Some(mut row) = generated_instance.clone_row(row_index) else { continue };
let local_translation = DAffine2::from_translation(row.transform.translation);
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
row.transform = local_translation * transform * local_matrix;
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let local_translation = DAffine2::from_translation(local_transform.translation);
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
*row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix;
result_table.push(row);
}
@@ -152,7 +154,10 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
) -> Table<T> {
let mut result_table = Table::new();
for TableRowRef { element: points, transform, .. } in points.iter() {
for points_index in 0..points.len() {
let Some(points_element) = points.element(points_index) else { continue };
let transform: DAffine2 = points.attribute_cloned_or_default("transform", points_index);
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
@@ -160,12 +165,12 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
let generated_instance = instance.eval(new_ctx.into_context()).await;
for mut generated_row in generated_instance.into_iter() {
generated_row.transform.translation = transformed_point;
generated_row.attribute_mut_or_insert_default::<DAffine2>("transform").translation = transformed_point;
result_table.push(generated_row);
}
};
let range = points.point_domain.positions().iter().enumerate();
let range = points_element.point_domain.positions().iter().enumerate();
if reverse {
for (index, &point) in range.rev() {
iteration(index, point).await;
@@ -228,8 +233,12 @@ mod test {
let points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));
let generated = super::repeat_on_points(context, points, &rect, false).await;
assert_eq!(generated.len(), positions.len());
for (position, generated_row) in positions.into_iter().zip(generated.iter()) {
let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
for (position, index) in positions.into_iter().zip(0..generated.len()) {
let bounds = generated
.element(index)
.unwrap()
.bounding_box_with_transform(generated.attribute_cloned_or_default("transform", index))
.unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);
}
@@ -249,7 +258,7 @@ mod test {
)
.await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.iter().next().unwrap().element;
let vector = vector_table.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 3);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
@@ -270,7 +279,7 @@ mod test {
)
.await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.iter().next().unwrap().element;
let vector = vector_table.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
@@ -282,7 +291,7 @@ mod test {
let context = OwnedContextImpl::default().into_context();
let repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await;
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.iter().next().unwrap().element;
let vector = vector_table.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {