Expand description
§flense
Purpose-oriented lensing for Rust.
§State
This is not production ready and should not be relied on. The tests pass under miri, but no thorough deep validation of the pointers and logic has been performed that would give a high degree of confidence. Stacked borrows are hard.
§High-level
Rather than focusing on accessing specific fields of specific structures, flense allows a sort of duck-typing for lenses, where you annotate purpose-oriented fields like Position or Color (or whatever you please!), then define how to adapt the data inside your structure into flense.
Other lensing libraries can only lense the original structure and its original fields. You can still achieve that with flense by creating a specialized Field for each specific structure field.
Invalid mutable lenses which alias data should be prevented at compile time, though cargo check may fail to find them when cargo build does.
§Properties
Creating a lens is simple: some_data.lens(). Type inference drives the creation of the correct lens.
This crate should largely compile out and be zero overhead. Functions taking reified lenses have a small performance overhead since the size of a lens is dependent on the number of fields in it.
§Examples
Example of adapting positions in 3D space, like from a Vertex.
use flense::prelude::*;
// A plain old data type, defined anywhere - if it was defined outside your lib,
// you could wrap it in a #[repr(transparent)] wrapper and pierce through it.
#[derive(Adapter, Clone, Copy, Default)]
struct Vertex {
// Derive correct `Adapter` implementations in structure definitions,
// or attach them later by writing your own `unsafe impl Adapter`.
#[field(new Normal)]
pub normal: [f32; 2],
// A "new reflexive" `Adapter` also writes an `Adapter` implementation for
// the underlying type; here, this lets you treat `[f32; 3]` as a Position
// as well.
#[field(new reflexive Position)]
pub position: [f32; 3],
#[field(new ColorRgb)]
pub color_rgb: [u8; 3],
}
// Or it's relatively straightforward to implement a reflexive adapter yourself
// if you want full control.
unsafe impl Adapter<ColorRgb> for <ColorRgb as Field>::Type {
const OFFSET: usize = 0;
}
// Somewhere else, possibly even provided in a library which is unaware of the
// concrete Vertex definition entirely.
fn compute_bounds<'a>(positions: impl LensesSlice<'a, (Position,)>) -> [f32; 3] {
[0.0, 0.0, 0.0] // todo!()
}
// Or, you can avoid performing code generation of the body multiple times by
// separating the lens construction from its usage, though this may prevent
// optimizations like vectorization.
#[inline]
fn compute_bounds_outer<'a>(positions: impl LensesSlice<'a, (Position,)>) -> [f32; 3] {
compute_bounds_inner(positions.lens_slice())
}
fn compute_bounds_inner(positions: LensSlice<(Position,)>) -> [f32; 3] {
// This inner function has no generic parameters whatsoever, but the outer
// function adapts any structure that can be lensed to provide a position!
// This lets you reduce binary size by avoiding monomorphization for every
// type that has a `Position`.
[0.0, 0.0, 0.0] // todo!()
}
fn main() {
let some_vertices = vec![Vertex::default(); 100];
compute_bounds(&some_vertices);
compute_bounds_outer(&some_vertices);
let some_contiguous_data = &[[0.0f32, 0.0, 0.0]; 100][..];
compute_bounds(some_contiguous_data);
compute_bounds_outer(some_contiguous_data);
}Working with either structure-of-arrays or array-of-structures
use flense::prelude::*;
use std::mem::offset_of;
#[derive(Adapter, Clone, Copy, Default)]
struct ParticleAos {
#[field(new reflexive Position)]
pub position: [f32; 2],
#[field(new reflexive Velocity)]
pub velocity: [f32; 2],
}
// hacky drag for the velocities via linear decay
fn approach_zero(value: f32, amount: f32) -> f32 {
(value.abs() - amount).max(0.0) * value.signum()
}
// positions is guaranteed to be rectangular, so you don't have to worry about
// one slice being longer or shorter than the other
fn step_particles<'a>(dt: f32, particles: impl LensesSliceMut<'a, (Position, Velocity)>) {
for lens in particles.lens_slice_mut() {
let (mut position, mut velocity) = lens.split::<(Position,), _>();
let position = position.as_mut::<Position, _>();
let velocity = velocity.as_mut::<Velocity, _>();
position[0] += velocity[0] * dt;
position[1] += velocity[1] * dt;
velocity[0] = approach_zero(velocity[0], dt);
velocity[1] = approach_zero(velocity[1], dt);
}
}
// or you can take multiple lenses for more flexibility
fn step_particles_multi<'a>(
dt: f32,
positions: impl LensesSliceMut<'a, (Position,)>,
velocities: impl LensesSliceMut<'a, (Velocity,)>
) {
let positions = positions.lens_slice_mut();
let velocities = velocities.lens_slice_mut();
for (mut position, mut velocity) in positions.into_iter().zip(velocities) {
let position = position.as_mut::<Position, _>();
let velocity = velocity.as_mut::<Velocity, _>();
position[0] += velocity[0] * dt;
position[1] += velocity[1] * dt;
velocity[0] = approach_zero(velocity[0], dt);
velocity[1] = approach_zero(velocity[1], dt);
}
}
fn main() {
let mut particles_aos = vec![
ParticleAos { position: [0.0, 0.0], velocity: [2.0, 0.0] },
ParticleAos { position: [0.0, 0.0], velocity: [0.0, 2.0] },
];
// Use with an array of structures...
step_particles(1.0, &mut particles_aos);
{
let lens: LensSliceMut<'_, (Position, Velocity)> = particles_aos.lens_slice_mut();
let (lhs, rhs) = lens.split::<_, _>();
step_particles_multi(1.0, lhs, rhs);
}
assert_eq!(particles_aos[0].position, [3.0, 0.0]);
assert_eq!(particles_aos[1].position, [0.0, 3.0]);
let mut soa_positions = vec![[0.0, 0.0]; 2];
let mut soa_velocities = vec![[2.0, 0.0], [0.0, 2.0]];
// Or with a structure of arrays! quality of life is a work in progress
step_particles(
1.0,
// form one lens by joining two separate ones - these could live in
// entirely different data structures or parts of memory
try_join_lens_mut::<(Position,), (Velocity,), 1>(
&mut soa_positions,
&mut soa_velocities,
).unwrap()
);
step_particles_multi(
1.0,
&mut soa_positions,
&mut soa_velocities,
);
assert_eq!(soa_positions[0], [3.0, 0.0]);
assert_eq!(soa_positions[1], [0.0, 3.0]);
}§Prior art, other lenses
flense was heavily inspired by some work I’ve done in creating a sound wrapper for meshoptimizer (which will eventually be published). In doing so, I explored concepts used in terrors and frunk.
grist_lensis another lens library which I saw the announcement of a few days before separating this one from mymeshoptimizerwrapper, though I haven’t used it. I did, however, read the author’s blog post going over the various lens options she has seen and used.lens-rspl-lens
§no-std
This crate is no-std and no-alloc.
§unsafe
Several traits in this crate are unsafe, like Adapter. If they are
implemented incorrectly, you will cause memory unsafety.
Modules§
- dim
- Concrete dimension storage, used both for sizes and strides.
- lens
- Concrete lens definitions.
- lenses
- Lensing trait definitions.
- prelude
- Prelude for
flensewith commonly imported items. - type_
lists - Type-level arithmetic used for set operations.