Skip to content

Commit 74a361a

Browse files
authored
Share list attributes between clones and read vector pool paths without copying them (#4739)
* Clear two Clippy lints from the vector pool path structure code * Find anchor tangents in one pass along the contours * Share a list's attributes between its clones until one is written to * Read each path's curve type, references, and IDs once when resolving its spans * Gather attribute values in a buffer on their way to a trait-object hasher * Append woven paths to their list as one piece * Read an item's paths without copying them and leave them shared when only points move * Weave any number of bezpaths in one pass, looking up the IDs to continue from once * Hold a list's lone attribute in place, sparing each path a collection of its own * Look up the paths' curve types once in the loops that walk every path
1 parent ab99e0e commit 74a361a

16 files changed

Lines changed: 1447 additions & 600 deletions

File tree

‎node-graph/libraries/core-types/src/list.rs‎

Lines changed: 754 additions & 274 deletions
Large diffs are not rendered by default.

‎node-graph/libraries/vector-types/src/vector/addressing.rs‎

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::connectivity::Connectivity;
22
use super::misc::HandleId;
3-
use super::path::{CurveType, Path, curve_type_at};
3+
use super::path::{CurveType, Path, curve_type_at, curve_types_of};
44
use super::vector_attributes::{PointId, SegmentId};
55
use core_types::list::{ATTR_ID, List};
66
use glam::DVec2;
@@ -27,9 +27,46 @@ impl Span {
2727

2828
/// Every span of every path, in path order.
2929
pub fn spans(paths: &List<Path>) -> impl Iterator<Item = Span> + '_ {
30-
paths.iter_element_values().enumerate().flat_map(|(path_index, path)| {
31-
let curve_type = curve_type_at(paths, path_index);
32-
(0..path.segment_count(curve_type) as u32).map(move |index| Span { path: path_index as u32, index })
30+
let paths_with_curve_types = paths.iter_element_values().zip(curve_types_of(paths)).enumerate();
31+
32+
paths_with_curve_types.flat_map(|(path_index, (path, curve_type))| (0..path.segment_count(curve_type) as u32).map(move |index| Span { path: path_index as u32, index }))
33+
}
34+
35+
/// A span together with what its path holds for it.
36+
pub(crate) struct ResolvedSpan<'a> {
37+
pub span: Span,
38+
pub path: &'a Path,
39+
pub curve_type: CurveType,
40+
/// The span's stable ID, absent when its path names no spans.
41+
pub id: Option<SegmentId>,
42+
/// The pool points the span runs between.
43+
pub endpoints: (u32, u32),
44+
}
45+
46+
/// Every span of every path in path order, resolved in O(references) by reading each path's curve type, references, and IDs once.
47+
pub(crate) fn resolved_spans(paths: &List<Path>) -> impl Iterator<Item = ResolvedSpan<'_>> + '_ {
48+
paths.iter_element_values().zip(curve_types_of(paths)).enumerate().flat_map(move |(path_index, (path, curve_type))| {
49+
let stride = curve_type.stride();
50+
let references = path.0.iter_element_values().as_slice();
51+
let ids = path.0.iter_attribute_values::<SegmentId>(ATTR_ID).map(|ids| ids.as_slice());
52+
53+
(0..path.segment_count(curve_type)).filter_map(move |index| {
54+
let reference = index * stride;
55+
let endpoints = (*references.get(reference)? as u32, *references.get(reference + stride)? as u32);
56+
let span = Span {
57+
path: path_index as u32,
58+
index: index as u32,
59+
};
60+
let id = ids.and_then(|ids| ids.get(reference).copied());
61+
62+
Some(ResolvedSpan {
63+
span,
64+
path,
65+
curve_type,
66+
id,
67+
endpoints,
68+
})
69+
})
3370
})
3471
}
3572

@@ -44,7 +81,7 @@ pub fn segment_id_of(paths: &List<Path>, span: Span) -> Option<SegmentId> {
4481

4582
/// The span a segment ID names, found by scanning every span in O(references).
4683
pub fn span_of_segment_id(paths: &List<Path>, segment: SegmentId) -> Option<Span> {
47-
spans(paths).find(|&span| segment_id_of(paths, span) == Some(segment))
84+
resolved_spans(paths).find(|resolved| resolved.id == Some(segment)).map(|resolved| resolved.span)
4885
}
4986

5087
/// The pool points a span runs between.

‎node-graph/libraries/vector-types/src/vector/connectivity.rs‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::path::{CurveType, Path, curve_type_at};
1+
use super::path::{CurveType, Path, curve_types_of};
22
use core_types::list::List;
33

44
/// One path's reference to a pool point, naming where in the weave that point is used.
@@ -49,9 +49,8 @@ impl Connectivity {
4949

5050
let mut cursors = starts[..point_count].to_vec();
5151
let mut references = vec![Reference::default(); starts[point_count] as usize];
52-
for (path_index, path) in paths.iter_element_values().enumerate() {
53-
// Each path's curve type is read once here, so queries never look it up again
54-
let curve_type = curve_type_at(paths, path_index);
52+
// Each path's curve type is read once here, so queries never look it up again
53+
for ((path_index, path), curve_type) in paths.iter_element_values().enumerate().zip(curve_types_of(paths)) {
5554
let last = path.0.len().saturating_sub(1);
5655

5756
for (reference_index, point) in path.references().enumerate() {

‎node-graph/libraries/vector-types/src/vector/contour.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::misc::dvec2_to_point;
2-
use super::path::{CurveType, Path, append_segment, curve_type_at};
2+
use super::path::{CurveType, Path, append_segment, curve_types_of};
33
use core_types::list::List;
44
use glam::DVec2;
55
use kurbo::BezPath;
@@ -94,7 +94,7 @@ pub fn contours(point_count: usize, paths: &List<Path>) -> Vec<Contour> {
9494

9595
/// The curve type of every path, read once so the stitching below never looks one up per path.
9696
fn curve_types(paths: &List<Path>) -> Vec<CurveType> {
97-
(0..paths.len()).map(|index| curve_type_at(paths, index)).collect()
97+
curve_types_of(paths).collect()
9898
}
9999

100100
fn contours_with(point_count: usize, paths: &List<Path>, curve_types: &[CurveType]) -> Vec<Contour> {

‎node-graph/libraries/vector-types/src/vector/flatten.rs‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::misc::{BezierHandles, HandleId, HandleType, segment_to_handles};
2-
use super::path::{CurveType, Path, continues_previous_path, curve_type_at, push_path};
2+
use super::path::{CurveType, Path, continues_previous_path, curve_types_of, push_paths};
33
use super::vector_attributes::{PointId, SegmentId};
44
use super::vector_types::Vector;
55
use core_types::list::{ATTR_ID, List};
@@ -44,8 +44,7 @@ impl WorkingVector {
4444
let working_point = |pool_index: u32| pool_to_working.get(pool_index as usize).copied().flatten();
4545

4646
// One pass so a span that fails to resolve leaves every array at the same length
47-
for (path_index, path) in paths.iter_element_values().enumerate() {
48-
let curve_type = curve_type_at(paths, path_index);
47+
for ((path_index, path), curve_type) in paths.iter_element_values().enumerate().zip(curve_types_of(paths)) {
4948
let stride = curve_type.stride();
5049
let span_ids = path.0.iter_attribute_values::<SegmentId>(ATTR_ID).map(|ids| ids.as_slice()).unwrap_or_default();
5150

@@ -223,9 +222,7 @@ impl WorkingVector {
223222
vector.0.set_attribute_values(ATTR_ID, point_ids);
224223
}
225224

226-
for (curve_type, references, ids) in runs {
227-
push_path(paths, curve_type, references, ids);
228-
}
225+
push_paths(paths, runs);
229226

230227
// G1 pairs narrow to the smooth flag on the anchor both handles face, and other pairings are dropped
231228
let anchor = |handle: HandleId| {
@@ -331,9 +328,9 @@ pub(crate) fn chain_order(point_count: usize, endpoints: &[(usize, usize)], dire
331328
let mut partner = vec![usize::MAX; segment_count * 2];
332329
for ends in ends_at.iter().filter(|ends| ends.len() >= 2) {
333330
if ends.len() > MOST_ENDS_PAIRED_BY_STRAIGHTNESS {
334-
for pair in ends.chunks_exact(2) {
335-
partner[pair[0]] = pair[1];
336-
partner[pair[1]] = pair[0];
331+
for &[first, second] in ends.as_chunks::<2>().0 {
332+
partner[first] = second;
333+
partner[second] = first;
337334
}
338335
continue;
339336
}
@@ -509,6 +506,7 @@ pub fn vector_from_legacy_parts(
509506
#[cfg(test)]
510507
mod tests {
511508
use super::*;
509+
use crate::vector::path::{curve_type_at, push_path};
512510
use core_types::list::{ATTR_CURVE_TYPE, ATTR_ID, ATTR_SMOOTH, Item};
513511

514512
fn working(points: usize, segments: &[(usize, usize, BezierHandles)]) -> WorkingVector {

‎node-graph/libraries/vector-types/src/vector/geometry.rs‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,6 @@ impl VectorGeometry {
222222
self.vector.bezpath_from_segments_ignore_discontinuities(segments)
223223
}
224224

225-
pub fn point_tangent(&self, point_index: usize) -> Option<DVec2> {
226-
self.vector.point_tangent(&self.paths, point_index)
227-
}
228-
229225
/// The manipulator's position in layer space. See [`ManipulatorPointId::get_position`].
230226
pub fn position_of(&self, point: ManipulatorPointId) -> Option<DVec2> {
231227
point.get_position(&self.vector, &self.paths)

0 commit comments

Comments
 (0)