mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 06:08:11 +08:00
Decompose the flat index over the content extent in structure creators
This commit is contained in:
@@ -39,6 +39,18 @@ pub trait ExtractIndex {
|
|||||||
fn innermost_index(&self) -> u64 {
|
fn innermost_index(&self) -> u64 {
|
||||||
self.try_index().and_then(|mut indices| indices.next()).unwrap_or(0) as u64
|
self.try_index().and_then(|mut indices| indices.next()).unwrap_or(0) as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The decompose half of decompose-and-promote: splits the flat innermost
|
||||||
|
/// index over the content's inner extent into the pushed level's copy and
|
||||||
|
/// the lane within it. Rectangular domains; an empty inner extent maps
|
||||||
|
/// everything to copy 0.
|
||||||
|
fn split_innermost(&self, inner: u64) -> (u64, u64) {
|
||||||
|
let flat = self.innermost_index();
|
||||||
|
match inner {
|
||||||
|
0 => (0, 0),
|
||||||
|
inner => (flat / inner, flat % inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub trait ExtractVarArgs {
|
pub trait ExtractVarArgs {
|
||||||
// TODO: Consider returning a slice or something like that
|
// TODO: Consider returning a slice or something like that
|
||||||
@@ -721,6 +733,7 @@ pub trait DeriveCtx {
|
|||||||
fn position_head(&self) -> Option<&PositionLink<'_>>;
|
fn position_head(&self) -> Option<&PositionLink<'_>>;
|
||||||
fn varargs_head(&self) -> Option<&VarArgLink<'_>>;
|
fn varargs_head(&self) -> Option<&VarArgLink<'_>>;
|
||||||
fn promoted<'s>(&'s self, spilled_head: &'s IndexLink<'s>, inner_index: u64) -> Derived<'s, Self>;
|
fn promoted<'s>(&'s self, spilled_head: &'s IndexLink<'s>, inner_index: u64) -> Derived<'s, Self>;
|
||||||
|
fn push_level<'s>(&'s self, frame: &'s mut IndexLink<'s>, copy: u64, inner: u64) -> Derived<'s, Self>;
|
||||||
fn with_footprint<'s>(&'s self, footprint: &'s Footprint) -> Derived<'s, Self>;
|
fn with_footprint<'s>(&'s self, footprint: &'s Footprint) -> Derived<'s, Self>;
|
||||||
fn with_varargs<'s>(&'s self, varargs: &'s VarArgLink<'s>) -> Derived<'s, Self>;
|
fn with_varargs<'s>(&'s self, varargs: &'s VarArgLink<'s>) -> Derived<'s, Self>;
|
||||||
fn with_position<'s>(&'s self, position: &'s PositionLink<'s>) -> Derived<'s, Self>;
|
fn with_position<'s>(&'s self, position: &'s PositionLink<'s>) -> Derived<'s, Self>;
|
||||||
@@ -1111,6 +1124,10 @@ impl<'a> DeriveCtx for ContextImpl<'a> {
|
|||||||
ContextImpl::promoted(self, spilled_head, inner_index)
|
ContextImpl::promoted(self, spilled_head, inner_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_level<'s>(&'s self, frame: &'s mut IndexLink<'s>, copy: u64, inner: u64) -> ContextImpl<'s> {
|
||||||
|
ContextImpl::push_level(self, frame, copy, inner)
|
||||||
|
}
|
||||||
|
|
||||||
fn with_footprint<'s>(&'s self, footprint: &'s Footprint) -> ContextImpl<'s> {
|
fn with_footprint<'s>(&'s self, footprint: &'s Footprint) -> ContextImpl<'s> {
|
||||||
ContextImpl::with_footprint(self, footprint)
|
ContextImpl::with_footprint(self, footprint)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -729,15 +729,17 @@ pub struct RecordLazyInput<'a, 'e, N> {
|
|||||||
node: &'a N,
|
node: &'a N,
|
||||||
cell: &'a crate::node::StatusCell,
|
cell: &'a crate::node::StatusCell,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
|
inner_levels: u8,
|
||||||
_lifetime: std::marker::PhantomData<fn() -> RecordValue<'e>>,
|
_lifetime: std::marker::PhantomData<fn() -> RecordValue<'e>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'e, N> RecordLazyInput<'a, 'e, N> {
|
impl<'a, 'e, N> RecordLazyInput<'a, 'e, N> {
|
||||||
pub fn new(node: &'a N, cell: &'a crate::node::StatusCell, input_index: usize) -> Self {
|
pub fn new(node: &'a N, cell: &'a crate::node::StatusCell, input_index: usize, inner_levels: u8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
node,
|
node,
|
||||||
cell,
|
cell,
|
||||||
input_index,
|
input_index,
|
||||||
|
inner_levels,
|
||||||
_lifetime: std::marker::PhantomData,
|
_lifetime: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -748,6 +750,36 @@ impl<'a, 'e, N> RecordLazyInput<'a, 'e, N> {
|
|||||||
{
|
{
|
||||||
Ok(self.node.eval_derived(self.cell, self.input_index, ctx)?.rebind())
|
Ok(self.node.eval_derived(self.cell, self.input_index, ctx)?.rebind())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The flat lane count of one copy: the product of the edge's inner-level
|
||||||
|
/// extents, queried uniform across copies (at copy 0). The dividend of a
|
||||||
|
/// structure node's decompose-and-promote.
|
||||||
|
pub fn inner_extent<B>(&self, ctx: &B) -> Result<u64, crate::gpoll::Interrupt>
|
||||||
|
where
|
||||||
|
B: crate::context::DeriveCtx,
|
||||||
|
N: for<'d> DerivedRecordEdge<'d, crate::context::Derived<'d, B>>,
|
||||||
|
{
|
||||||
|
inner_extent_of(self.node, ctx, self.inner_levels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`RecordLazyInput::inner_extent`].
|
||||||
|
fn inner_extent_of<B, N>(node: &N, ctx: &B, levels: u8) -> Result<u64, crate::gpoll::Interrupt>
|
||||||
|
where
|
||||||
|
B: crate::context::DeriveCtx,
|
||||||
|
N: for<'d> DerivedRecordEdge<'d, crate::context::Derived<'d, B>>,
|
||||||
|
{
|
||||||
|
let head = ctx.index_head();
|
||||||
|
let derived = ctx.promoted(&head, 0);
|
||||||
|
let mut inner: u64 = 1;
|
||||||
|
for level in 0..levels {
|
||||||
|
match node.extent_at_derived(&derived, level) {
|
||||||
|
GPoll::Final(crate::gpoll::Extent::Exactly(count)) => inner *= count as u64,
|
||||||
|
GPoll::Pending => return Err(crate::gpoll::Interrupt::Pending),
|
||||||
|
_ => return Err(crate::gpoll::GraphError::new("structure decomposition over a non-exact extent").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The derive-routing carrier beside its declared attribute reads: evaluating
|
/// The derive-routing carrier beside its declared attribute reads: evaluating
|
||||||
@@ -759,6 +791,7 @@ pub struct DerivedLazyInput<'a, 'e, Out, N> {
|
|||||||
node: &'a N,
|
node: &'a N,
|
||||||
cell: &'a crate::node::StatusCell,
|
cell: &'a crate::node::StatusCell,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
|
inner_levels: u8,
|
||||||
reads: &'a [Option<usize>],
|
reads: &'a [Option<usize>],
|
||||||
read: unsafe fn(Rec, &[Option<usize>]) -> Out,
|
read: unsafe fn(Rec, &[Option<usize>]) -> Out,
|
||||||
_lifetime: std::marker::PhantomData<fn() -> RecordValue<'e>>,
|
_lifetime: std::marker::PhantomData<fn() -> RecordValue<'e>>,
|
||||||
@@ -767,17 +800,27 @@ pub struct DerivedLazyInput<'a, 'e, Out, N> {
|
|||||||
impl<'a, 'e, Out, N> DerivedLazyInput<'a, 'e, Out, N> {
|
impl<'a, 'e, Out, N> DerivedLazyInput<'a, 'e, Out, N> {
|
||||||
/// `read` must be sound against the layout the offsets in `reads` were
|
/// `read` must be sound against the layout the offsets in `reads` were
|
||||||
/// resolved from; the macro proves both at wiring.
|
/// resolved from; the macro proves both at wiring.
|
||||||
pub fn new(node: &'a N, cell: &'a crate::node::StatusCell, input_index: usize, reads: &'a [Option<usize>], read: unsafe fn(Rec, &[Option<usize>]) -> Out) -> Self {
|
pub fn new(node: &'a N, cell: &'a crate::node::StatusCell, input_index: usize, inner_levels: u8, reads: &'a [Option<usize>], read: unsafe fn(Rec, &[Option<usize>]) -> Out) -> Self {
|
||||||
Self {
|
Self {
|
||||||
node,
|
node,
|
||||||
cell,
|
cell,
|
||||||
input_index,
|
input_index,
|
||||||
|
inner_levels,
|
||||||
reads,
|
reads,
|
||||||
read,
|
read,
|
||||||
_lifetime: std::marker::PhantomData,
|
_lifetime: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The flat lane count of one copy; see [`RecordLazyInput::inner_extent`].
|
||||||
|
pub fn inner_extent<B>(&self, ctx: &B) -> Result<u64, crate::gpoll::Interrupt>
|
||||||
|
where
|
||||||
|
B: crate::context::DeriveCtx,
|
||||||
|
N: for<'d> DerivedRecordEdge<'d, crate::context::Derived<'d, B>>,
|
||||||
|
{
|
||||||
|
inner_extent_of(self.node, ctx, self.inner_levels)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eval<'d, C>(&self, ctx: &C) -> Result<Out, crate::gpoll::Interrupt>
|
pub fn eval<'d, C>(&self, ctx: &C) -> Result<Out, crate::gpoll::Interrupt>
|
||||||
where
|
where
|
||||||
N: DerivedRecordEdge<'d, C>,
|
N: DerivedRecordEdge<'d, C>,
|
||||||
@@ -1399,6 +1442,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extent_at(&self, input: &C, level: u8) -> GPoll<crate::gpoll::Extent> {
|
||||||
|
self.edge.extent_at(input, level)
|
||||||
|
}
|
||||||
|
|
||||||
fn layout(&self) -> &Layout {
|
fn layout(&self) -> &Layout {
|
||||||
&self.union
|
&self.union
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1161,20 +1161,20 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// A raw poll edge is threaded straight through, so it does not bind here.
|
// A raw poll edge is threaded straight through, so it does not bind here.
|
||||||
(LazyBinding::Plain, true) => quote!(),
|
(LazyBinding::Plain, true) => quote!(),
|
||||||
(LazyBinding::DeriveRouting, _) => quote! {
|
(LazyBinding::DeriveRouting, _) => quote! {
|
||||||
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index);
|
let #name = #core_types::record::RecordLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels));
|
||||||
},
|
},
|
||||||
(LazyBinding::DeriveCarrier, _) => {
|
(LazyBinding::DeriveCarrier, _) => {
|
||||||
let reads = reads_of(index);
|
let reads = reads_of(index);
|
||||||
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
let read_fn = format_ident!("__{}_read_{}", fn_name, index);
|
||||||
match reads.is_empty() {
|
match reads.is_empty() {
|
||||||
true => quote! {
|
true => quote! {
|
||||||
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, &[], #core_types::record::token_only);
|
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &[], #core_types::record::token_only);
|
||||||
},
|
},
|
||||||
false => {
|
false => {
|
||||||
let slot_idents: Vec<Ident> = reads.iter().map(|(slot, _)| format_ident!("__read_{slot}")).collect();
|
let slot_idents: Vec<Ident> = reads.iter().map(|(slot, _)| format_ident!("__read_{slot}")).collect();
|
||||||
quote! {
|
quote! {
|
||||||
let __carrier_reads = [#(self.#slot_idents),*];
|
let __carrier_reads = [#(self.#slot_idents),*];
|
||||||
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, &__carrier_reads, self::#read_fn);
|
let #name = #core_types::record::DerivedLazyInput::new(&self.#name, &__cell, #index, self.__layout.depth.saturating_sub(#pushed_levels), &__carrier_reads, self::#read_fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! wiring is by hand until the compiler pass constructs layouts.
|
//! wiring is by hand until the compiler pass constructs layouts.
|
||||||
|
|
||||||
use core_types::attribute::{Attr, Opacity, RemoveAttr};
|
use core_types::attribute::{Attr, Opacity, RemoveAttr};
|
||||||
use core_types::context::{DeriveCtx, ExtractArena, ExtractIndex, InjectIndex};
|
use core_types::context::{DeriveCtx, ExtractArena, ExtractIndex, IndexLink, InjectIndex};
|
||||||
use core_types::extent::{ExtentIn, LevelIn, ValueIn};
|
use core_types::extent::{ExtentIn, LevelIn, ValueIn};
|
||||||
use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt};
|
use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt};
|
||||||
use core_types::{Context, Ctx};
|
use core_types::{Context, Ctx};
|
||||||
@@ -82,13 +82,15 @@ fn repeat<T>(
|
|||||||
count: u32,
|
count: u32,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
) -> Result<IList<T>, Interrupt> {
|
) -> Result<IList<T>, Interrupt> {
|
||||||
let spilled = ctx.index_head();
|
let inner = content.inner_extent(ctx)?;
|
||||||
let copy = ctx.innermost_index() % count as u64;
|
let (copy, rest) = ctx.split_innermost(inner);
|
||||||
|
let copy = copy % count.max(1) as u64;
|
||||||
let copy = match reverse {
|
let copy = match reverse {
|
||||||
true => count as u64 - 1 - copy,
|
true => count as u64 - 1 - copy,
|
||||||
false => copy,
|
false => copy,
|
||||||
};
|
};
|
||||||
content.eval(&ctx.promoted(&spilled, copy))
|
let mut frame = IndexLink { index: 0, outer: None };
|
||||||
|
content.eval(&ctx.push_level(&mut frame, copy, rest))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pushed level's extent is the copy count; inner levels forward to the
|
/// The pushed level's extent is the copy count; inner levels forward to the
|
||||||
@@ -108,9 +110,11 @@ fn repeat_faded<T>(
|
|||||||
content: impl Node<Context<'_>, Output = (T, Attr<Opacity>)>,
|
content: impl Node<Context<'_>, Output = (T, Attr<Opacity>)>,
|
||||||
count: u32,
|
count: u32,
|
||||||
) -> Result<IList<(T, Attr<Opacity>)>, Interrupt> {
|
) -> Result<IList<(T, Attr<Opacity>)>, Interrupt> {
|
||||||
let spilled = ctx.index_head();
|
let inner = content.inner_extent(ctx)?;
|
||||||
let copy = ctx.innermost_index() % count as u64;
|
let (copy, rest) = ctx.split_innermost(inner);
|
||||||
let (element, opacity) = content.eval(&ctx.promoted(&spilled, copy))?;
|
let copy = copy % count.max(1) as u64;
|
||||||
|
let mut frame = IndexLink { index: 0, outer: None };
|
||||||
|
let (element, opacity) = content.eval(&ctx.push_level(&mut frame, copy, rest))?;
|
||||||
Ok(emit(element, Attr(*opacity * (copy + 1) as f64)))
|
Ok(emit(element, Attr(*opacity * (copy + 1) as f64)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +267,9 @@ mod tests {
|
|||||||
type Output = RecordValue<'e>;
|
type Output = RecordValue<'e>;
|
||||||
|
|
||||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||||
let element = input.innermost_index() as f64;
|
// Depth-0 content varying per copy: the enclosing (pushed) level's
|
||||||
|
// index sits one link above the content's own innermost lane.
|
||||||
|
let element = input.try_index().and_then(|mut indices| indices.nth(1)).unwrap_or(0) as f64;
|
||||||
let mut value = RecordValue::zeroed();
|
let mut value = RecordValue::zeroed();
|
||||||
let dst = match self.layout.frame_bytes() {
|
let dst = match self.layout.frame_bytes() {
|
||||||
0 => value.as_mut_ptr(),
|
0 => value.as_mut_ptr(),
|
||||||
@@ -495,6 +501,55 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repeat_decomposes_the_flat_index_over_depth_one_content() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let base = f64_layout(&[]);
|
||||||
|
let leveled_content = repeat_opacity_layout(&base);
|
||||||
|
let (count_edge, count_layout) = lifted_value(2u32);
|
||||||
|
let (reverse_edge, reverse_layout) = lifted_value(false);
|
||||||
|
reserve_for(&[&base, &leveled_content, &count_layout, &reverse_layout]);
|
||||||
|
|
||||||
|
let content = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||||
|
let meta = core_types::record::LayoutMeta {
|
||||||
|
sources: vec![0],
|
||||||
|
reads: vec![],
|
||||||
|
element: core_types::record::ElementSpec::Carried,
|
||||||
|
writes: vec![],
|
||||||
|
removes: vec![],
|
||||||
|
level_delta: 1,
|
||||||
|
};
|
||||||
|
let repeat = install(
|
||||||
|
RepeatNode::new(RecordSource::new(content, &leveled_content, &leveled_content), count_edge, reverse_edge, &leveled_content, &count_layout, &reverse_layout),
|
||||||
|
meta,
|
||||||
|
&[Some(&leveled_content)],
|
||||||
|
);
|
||||||
|
let two_level = Node::<ContextImpl>::layout(&repeat).clone();
|
||||||
|
assert_eq!(two_level.depth, 2, "the pushed level sits above the content's own level");
|
||||||
|
assert_eq!(repeat.extent_at(&ctx, 1), GPoll::Final(Extent::Exactly(2)), "the pushed level's extent is the copy count");
|
||||||
|
assert_eq!(repeat.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)), "the content's level forwards");
|
||||||
|
|
||||||
|
let head = ctx.index_head();
|
||||||
|
for flat in 0..6u64 {
|
||||||
|
let mark = stack::sp();
|
||||||
|
let lane = ctx.promoted(&head, flat);
|
||||||
|
let GPoll::Final(value) = repeat.eval(&lane) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
let rec = two_level.rec(&value);
|
||||||
|
assert_eq!(unsafe { rec.element::<f64>() }, 7.);
|
||||||
|
// The flat index decomposes: the content sees the remainder as its
|
||||||
|
// own innermost lane, so its per-lane opacity is `flat % 3`.
|
||||||
|
assert_eq!(unsafe { rec.read::<f64>(two_level.offset_of(Opacity::NAME, 0).unwrap()) }, (flat % 3) as f64);
|
||||||
|
// SAFETY: the element and attr were read out above, so no borrow into this lane's frames remains.
|
||||||
|
unsafe { stack::rewind(mark) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lazy_carrier_reads_and_rewrites_the_attr_per_copy() {
|
fn lazy_carrier_reads_and_rewrites_the_attr_per_copy() {
|
||||||
let arena = Arena::new(1024).unwrap();
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use core::f64::consts::TAU;
|
use core::f64::consts::TAU;
|
||||||
use core_types::attribute::{Attr, Transform};
|
use core_types::attribute::{Attr, Transform};
|
||||||
use core_types::context::ExtractIndex;
|
use core_types::context::{ExtractIndex, IndexLink};
|
||||||
use core_types::extent::{ExtentIn, LevelIn, ValueIn};
|
use core_types::extent::{ExtentIn, LevelIn, ValueIn};
|
||||||
use core_types::gpoll::{Extent, GPoll, Interrupt};
|
use core_types::gpoll::{Extent, GPoll, Interrupt};
|
||||||
use core_types::registry::types::{Angle, PixelSize};
|
use core_types::registry::types::{Angle, PixelSize};
|
||||||
@@ -23,9 +23,11 @@ fn repeat_array<T>(
|
|||||||
#[hard(1..)]
|
#[hard(1..)]
|
||||||
count: u32,
|
count: u32,
|
||||||
) -> Result<IList<(T, Attr<Transform>)>, Interrupt> {
|
) -> Result<IList<(T, Attr<Transform>)>, Interrupt> {
|
||||||
let spilled = ctx.index_head();
|
let inner = content.inner_extent(ctx)?;
|
||||||
let copy = ctx.innermost_index() % count as u64;
|
let (copy, rest) = ctx.split_innermost(inner);
|
||||||
let (element, local) = content.eval(&ctx.promoted(&spilled, copy))?;
|
let copy = copy % count.max(1) as u64;
|
||||||
|
let mut frame = IndexLink { index: 0, outer: None };
|
||||||
|
let (element, local) = content.eval(&ctx.push_level(&mut frame, copy, rest))?;
|
||||||
|
|
||||||
// A single copy has no steps between copies, so the denominator stays 1.
|
// A single copy has no steps between copies, so the denominator stays 1.
|
||||||
let total = (count - 1).max(1) as f64;
|
let total = (count - 1).max(1) as f64;
|
||||||
@@ -58,9 +60,11 @@ fn repeat_radial<T>(
|
|||||||
#[hard(1..)]
|
#[hard(1..)]
|
||||||
count: u32,
|
count: u32,
|
||||||
) -> Result<IList<(T, Attr<Transform>)>, Interrupt> {
|
) -> Result<IList<(T, Attr<Transform>)>, Interrupt> {
|
||||||
let spilled = ctx.index_head();
|
let inner = content.inner_extent(ctx)?;
|
||||||
let copy = ctx.innermost_index() % count as u64;
|
let (copy, rest) = ctx.split_innermost(inner);
|
||||||
let (element, local) = content.eval(&ctx.promoted(&spilled, copy))?;
|
let copy = copy % count.max(1) as u64;
|
||||||
|
let mut frame = IndexLink { index: 0, outer: None };
|
||||||
|
let (element, local) = content.eval(&ctx.push_level(&mut frame, copy, rest))?;
|
||||||
|
|
||||||
let angle = DAffine2::from_angle((TAU / count as f64) * copy as f64 + start_angle.to_radians());
|
let angle = DAffine2::from_angle((TAU / count as f64) * copy as f64 + start_angle.to_radians());
|
||||||
let translation = DAffine2::from_translation(radius * DVec2::Y);
|
let translation = DAffine2::from_translation(radius * DVec2::Y);
|
||||||
|
|||||||
Reference in New Issue
Block a user