Closed Shfty closed 1 year ago
After some experimentation, it's looking like functional lenses (or some simplification thereof) are going to be the right way to make this work w.r.t. nested types; the type-tagged field approach works, but doesn't cover cases where the same struct is present at multiple nesting depths.
Should be able to keep the type-tagged field idea, and compose it into a cons list path that can be traversed via trait.
ex. Translate::<Square>::default().with((FieldSdf, (FieldOp, ())), 0.5)
, where the cons list could be composed using a type-level macro like path!(FieldSdf, FieldOp)
.
May be able to store field tag types on associated types or consts in a trait, and use them for lookup purposes; primary motivation being something like Translate<Square>::default().with(path!(Translate::Inner, Square::Size), 0.5)
, or perhaps even getting a pre-wrapped Square::Size
directly from Translate
.
Managed to make it work with all desired features, but need to setup a new repo to hold type-level path functionality, derive for all types, and implement per-shape const aliases before publishing.
Done.
Currently constructuing everything via struct update pattern + free-floating default function.
Ideal would be an inline builder pattern like
Translate::<Cube>::default().with_translation(Vec3::X).with_size(0.5)
, but a trivial deref-based implementation raises concerns about implicit conflicts in the returned pointer type.Consider
Translate<Cube>
, which expands out to:Operator<Operator<Point, ElongateOp<Exact, Vec3>, Vec3>, TranslateOp<Vec3>, Vec3>
Since a deref implementation would point to the SDF field, we're specifically interested in the
Operator -> Operator -> Point
chain. Builder-stylewith_foo(&mut self, foo: T) -> &mut Self
doesn't work here, since calling a method on one of the inner types would return a mutable reference that denies access to anything above it in the chain.So it seems that type level trait chaining, whereby outer types defer to inner types for implementation but short-circuit the return chain, is in order.
However, this raises its own concerns; specifically, what would these traits be? It seems impractical to try generalizing parameters into traits that are shared between types (ex.
trait Radius
ortrait Size<Dim>
), since that would result in many small traits and eventually scale poorly in terms of boilerplate.Thus, it seems like the remaining option is to implement one trait and virtualize parameter type through a generic param via marker structs. That way, wrappers can defer to their children from one callsite that generalizes over any type, and leaf types can expose parameters via marker traits.