diff --git a/crates/doc/src/pattern.rs b/crates/doc/src/pattern.rs index 6b9d902ba..1b587e70f 100644 --- a/crates/doc/src/pattern.rs +++ b/crates/doc/src/pattern.rs @@ -467,6 +467,14 @@ impl RepeatKind { RepeatKind::Mirror { .. } => "Mirror Repeat", } } + /// How many instances [`repeat_transforms`] makes (with its clamps). + fn count(&self) -> usize { + match self { + RepeatKind::Radial { instances, .. } => (*instances).clamp(1, 1000) as usize, + RepeatKind::Grid { rows, cols, .. } => (*rows).clamp(1, 500) as usize * (*cols).clamp(1, 500) as usize, + RepeatKind::Mirror { .. } => 2, + } + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -475,6 +483,23 @@ pub struct RepeatSpec { pub source: Vec>, } +/// Most instances a Repeat makes in all, those of the Repeats nested in its source included: as +/// many as one 500 × 500 grid, the budget [`PatternDef::offsets_covering`] also keeps. +const MAX_REPEAT_INSTANCES: usize = 250_000; + +/// Instances the Repeats in `nodes` make in all (0 when there are none). +fn nested_instances(nodes: &[Arc]) -> usize { + let mut sum = 0usize; + for n in nodes { + sum = sum.saturating_add(match (&n.kind, n.children()) { + (NodeKind::Repeat(r), _) => r.instances_in_all(), + (_, Some(c)) => nested_instances(c), + _ => 0, + }); + } + sum +} + /// Reflection across the line through `c` with direction angle `deg`. pub fn reflect_about(c: Point, deg: f64) -> Affine { let t = (2.0 * deg).to_radians(); @@ -536,7 +561,21 @@ impl RepeatSpec { crate::live::nodes_bounds(&self.source) } pub fn transforms(&self) -> Vec { - repeat_transforms(&self.kind, self.source_bounds()) + let mut v = repeat_transforms(&self.kind, self.source_bounds()); + // Each instance draws the Repeats in the source again, so nested ones multiply (a 500 × 500 + // grid of one is 62.5 billion instances): keep the first that fit the budget. + v.truncate(self.kept(nested_instances(&self.source))); + v + } + /// How many of its instances this Repeat keeps when each makes `nested` instances of the + /// Repeats in its source: as many as fit in [`MAX_REPEAT_INSTANCES`], at least one. + fn kept(&self, nested: usize) -> usize { + self.kind.count().min(MAX_REPEAT_INSTANCES / nested.max(1)).max(1) + } + /// Instances made in all, those of the Repeats nested in the source included. + fn instances_in_all(&self) -> usize { + let nested = nested_instances(&self.source); + self.kept(nested).saturating_mul(nested.max(1)) } /// Evaluated instances: one group of the (transformed) source per instance (ids 0). pub fn expand(&self) -> Vec { @@ -838,6 +877,35 @@ mod tests { assert!((angle.rem_euclid(180.0) - 0.0).abs() < 1e-9 || (angle.rem_euclid(180.0) - 180.0).abs() < 1e-9, "{angle}"); } + #[test] + fn nested_repeats_share_one_instance_budget() { + // Instances made in all, counted from the transforms without drawing any. + fn made(r: &RepeatSpec) -> usize { + let each: usize = r.source.iter().map(|s| if let NodeKind::Repeat(n) = &s.kind { made(n) } else { 1 }).sum(); + r.transforms().len().saturating_mul(each) + } + let grid = |source: Arc| { + let mut g = RepeatSpec::grid(vec![source], 0.0, 0.0); + if let RepeatKind::Grid { rows, cols, .. } = &mut g.kind { + *rows = 500; + *cols = 500; + } + g + }; + let repeat = |r: RepeatSpec| Arc::new(Node::new(NodeId(2), NodeKind::Repeat(r))); + // One grid keeps all its instances. + let inner = grid(sq(0.0, 0.0, 1.0)); + assert_eq!(made(&inner), 250_000); + // A 500 × 500 grid of it made 62.5 billion. + let outer = grid(repeat(inner.clone())); + assert!(made(&outer) <= 250_000, "{} instances", made(&outer)); + assert_eq!(outer.expand().len(), outer.transforms().len()); + assert!(made(&RepeatSpec::mirror(vec![repeat(inner)], 90.0, 5.0)) <= 250_000); + // Small nests are left as they are. + let small = RepeatSpec::mirror(vec![repeat(RepeatSpec::grid(vec![sq(0.0, 0.0, 10.0)], 1.0, 1.0))], 90.0, 5.0); + assert_eq!(made(&small), 18); + } + #[test] fn serde_roundtrip() { let mut def = PatternDef::new("Dots", vec![sq(0.0, 0.0, 10.0)]);