Made an actual working example!

This commit is contained in:
Chris Hodapp 2020-03-26 10:19:54 -04:00
parent 83053ca8bb
commit 7a6d29da81
3 changed files with 222 additions and 14 deletions

View File

@ -10,8 +10,6 @@ use crate::scratch;
fn cube_thing() -> Rule { fn cube_thing() -> Rule {
let mesh = prim::cube();
// Quarter-turn in radians: // Quarter-turn in radians:
let qtr = std::f32::consts::FRAC_PI_2; let qtr = std::f32::consts::FRAC_PI_2;
@ -28,24 +26,42 @@ fn cube_thing() -> Rule {
geometry::Rotation3::from_axis_angle(z, -qtr).to_homogeneous(), geometry::Rotation3::from_axis_angle(z, -qtr).to_homogeneous(),
]; ];
let rec = || -> RuleEval { let rec = move |self_: Rc<Rule>| -> RuleEval {
let gen_rulestep = |rot: &Mat4| -> Child { let gen_rulestep = |rot: &Mat4| -> Child {
let m: Mat4 = rot * let m: Mat4 = rot *
Matrix4::new_scaling(0.5) * Matrix4::new_scaling(0.5) *
geometry::Translation3::new(6.0, 0.0, 0.0).to_homogeneous(); geometry::Translation3::new(6.0, 0.0, 0.0).to_homogeneous();
Child { Child {
rule: Rule { eval: Box::new(rec) }, rule: self_.clone(),
xf: m, xf: m,
vmap: vec![], vmap: vec![],
} }
}; };
RuleEval { RuleEval {
geom: mesh.clone(), geom: prim::cube(),
final_geom: prim::empty_mesh(), final_geom: prim::empty_mesh(),
children: turns.iter().map(gen_rulestep).collect(), children: turns.iter().map(gen_rulestep).collect(),
} }
}; };
// I can't really do *mutual* recursion with the above, can I? I'd
// need actual functions for that.
// Also: 'turns' above is a problem. I can't clone it *inside* the
// closure because it doesn't live long enough (it is out of scope
// when the closure runs). I can't move it, or Fn becomes
// FnOnce. It doesn't implement Copy, and really can't, because it
// has vectors inside.
//
// (I guess I could use Rc<OpenMesh> instead if I want cheap
// support for Copy. Or... just put prim::cube() right inside.)
//
// what did I learn from this? That "constants" outside the
// closure only work the way I think they should work if:
// - they're actually static
// - they implement Copy
// - the closure can move them
Rule { eval: Box::new(rec) } Rule { eval: Box::new(rec) }
} }
@ -494,6 +510,15 @@ pub fn main() {
} }
*/ */
fn run_test_iter(r: &Rc<Rule>, iters: usize, name: &str) {
println!("Running {}...", name);
let (mesh, nodes) = Rule::to_mesh_iter(r.clone(), iters);
println!("Evaluated {} rules", nodes);
let fname = format!("{}.stl", name);
println!("Writing {}...", fname);
mesh.write_stl_file(&fname).unwrap();
}
/* /*
run_test(CubeThing::init(), Rule { eval: CubeThing::rec }, 3, "cube_thing"); run_test(CubeThing::init(), Rule { eval: CubeThing::rec }, 3, "cube_thing");
// this can't work on its own because the resultant OpenMesh still // this can't work on its own because the resultant OpenMesh still
@ -516,6 +541,11 @@ pub fn main() {
// let f = 20; // let f = 20;
// run_test_iter(Twist::init(f as f32, 32), 100*f, "twist2"); // run_test_iter(Twist::init(f as f32, 32), 100*f, "twist2");
let rule = Rc::new(cube_thing());
run_test_iter(&rule, 3, "cube_thing3");
if false
{ {
let a = vec![1,2,3]; let a = vec![1,2,3];

View File

@ -1,5 +1,6 @@
use crate::openmesh::{OpenMesh, Tag, Mat4}; use crate::openmesh::{OpenMesh, Tag, Mat4};
//use crate::prim; //use crate::prim;
use std::rc::Rc;
/// Definition of a rule. In general, a `Rule`: /// Definition of a rule. In general, a `Rule`:
/// ///
@ -7,7 +8,7 @@ use crate::openmesh::{OpenMesh, Tag, Mat4};
/// - tells what other rules to invoke, and what to do with their /// - tells what other rules to invoke, and what to do with their
/// geometry /// geometry
pub struct Rule { pub struct Rule {
pub eval: Box<dyn Fn() -> RuleEval>, pub eval: Box<dyn Fn(Rc<Rule>) -> RuleEval>,
} }
// TODO: It may be possible to have just a 'static' rule that requires // TODO: It may be possible to have just a 'static' rule that requires
// no function call. // no function call.
@ -51,7 +52,7 @@ pub struct RuleEval {
pub struct Child { pub struct Child {
/// Rule to evaluate to produce geometry /// Rule to evaluate to produce geometry
pub rule: Rule, pub rule: Rc<Rule>,
/// The transform to apply to all geometry produced by `rule` /// The transform to apply to all geometry produced by `rule`
/// (including its own `geom` and `final_geom` if needed, as well /// (including its own `geom` and `final_geom` if needed, as well
@ -79,11 +80,11 @@ impl Rule {
/// Convert this `Rule` to mesh data, recursively (depth first). /// Convert this `Rule` to mesh data, recursively (depth first).
/// `iters_left` sets the maximum recursion depth. This returns /// `iters_left` sets the maximum recursion depth. This returns
/// (geometry, number of rule evaluations). /// (geometry, number of rule evaluations).
pub fn to_mesh(&self, iters_left: u32) -> (OpenMesh, usize) { pub fn to_mesh(s: Rc<Self>, iters_left: u32) -> (OpenMesh, usize) {
let mut evals = 1; let mut evals = 1;
let rs: RuleEval = (self.eval)(); let rs: RuleEval = (s.eval)(s.clone());
if iters_left <= 0 { if iters_left <= 0 {
return (rs.final_geom, 1); return (rs.final_geom, 1);
// TODO: This is probably wrong because of the way that // TODO: This is probably wrong because of the way that
@ -96,7 +97,7 @@ impl Rule {
let subgeom: Vec<(OpenMesh, &Vec<usize>)> = rs.children.iter().map(|sub| { let subgeom: Vec<(OpenMesh, &Vec<usize>)> = rs.children.iter().map(|sub| {
// Get sub-geometry (still un-transformed): // Get sub-geometry (still un-transformed):
let (submesh, eval) = sub.rule.to_mesh(iters_left - 1); let (submesh, eval) = Rule::to_mesh(sub.rule.clone(), iters_left - 1);
// Tally up eval count: // Tally up eval count:
evals += eval; evals += eval;
@ -112,7 +113,7 @@ impl Rule {
/// This should be identical to to_mesh, but implemented /// This should be identical to to_mesh, but implemented
/// iteratively with an explicit stack rather than with recursive /// iteratively with an explicit stack rather than with recursive
/// function calls. /// function calls.
pub fn to_mesh_iter(&self, max_depth: usize) -> (OpenMesh, usize) { pub fn to_mesh_iter(s: Rc<Self>, max_depth: usize) -> (OpenMesh, usize) {
struct State { struct State {
// The set of rules we're currently handling: // The set of rules we're currently handling:
@ -133,7 +134,7 @@ impl Rule {
// (usually because they involve multiple rules). // (usually because they involve multiple rules).
// //
// We evaluate our own rule to initialize the stack: // We evaluate our own rule to initialize the stack:
let eval = (self.eval)(); let eval = (s.eval)(s.clone());
let mut stack: Vec<State> = vec![State { let mut stack: Vec<State> = vec![State {
rules: eval.children, rules: eval.children,
next: 0, next: 0,
@ -162,7 +163,7 @@ impl Rule {
// Evaluate the rule: // Evaluate the rule:
let child = &s.rules[s.next]; let child = &s.rules[s.next];
let mut eval = (child.rule.eval)(); let mut eval = (child.rule.eval)(child.rule.clone());
eval_count += 1; eval_count += 1;
// Make an updated world transform: // Make an updated world transform:

View File

@ -118,3 +118,180 @@ impl Foo5 {
} }
} }
// This is valid and I can recurse:
struct W {
b: Box<dyn Fn() -> W>,
}
struct Foo6 {}
impl Foo6 {
fn fn1(s: &Rc<Self>) -> W {
let s2 = Rc::clone(&s);
W { b: Box::new(move || Self::fn1(&s2)) }
}
fn fn2(s: &Rc<Self>) -> (W, W) {
let s2 = Rc::clone(&s);
let w2 = W { b: Box::new(move || Self::fn1(&s2)) };
let s3 = Rc::clone(&s);
let w3 = W { b: Box::new(move || Self::fn1(&s3)) };
(w2, w3)
}
}
fn foo6() {
// Whatever (note that it doesn't automatically do Copy):
struct State {
v: u32,
}
// Purposely put state somewhere it goes out of scope:
let s = {
let s_orig = State {
v: 105,
};
Rc::new(s_orig)
};
/*
let fn1 = |f: &dyn Fn(&dyn Fn() -> W) -> (&dyn Fn() -> W)| -> (&dyn Fn() -> W) {
&(|| -> W {
let s2 = Rc::clone(&s);
W { b: Box::new(move || f(f)) }
})
};
let f2 = fn1(fn1);
*/
}
fn foo7(t: impl Clone) -> impl Clone {
t.clone()
}
fn foo7b<T: Clone>(t: T) -> T {
t.clone()
}
fn foo7c<T>(t: T) -> T where T: Clone {
t.clone()
}
// A simple implementation of the Y Combinator
// λf.(λx.xx)(λx.f(xx))
// <=> λf.(λx.f(xx))(λx.f(xx))
// CREDITS: A better version of the previous code that was posted here, with detailed explanation.
// See <y> and also <y_apply>.
// A function type that takes its own type as an input is an infinite recursive type.
// We introduce a trait that will allow us to have an input with the same type as self, and break the recursion.
// The input is going to be a trait object that implements the desired function in the interface.
// NOTE: We will be coercing a reference to a closure into this trait object.
trait Apply<T, R> {
fn apply(&self, f: &dyn Apply<T, R>, t: T) -> R;
}
// In Rust, closures fall into three kinds: FnOnce, FnMut and Fn.
// FnOnce assumed to be able to be called just once if it is not Clone. It is impossible to
// write recursive FnOnce that is not Clone.
// All FnMut are also FnOnce, although you can call them multiple times, they are not allow to
// have a reference to themselves. So it is also not possible to write recursive FnMut closures
// that is not Clone.
// All Fn are also FnMut, and all closures of Fn are also Clone. However, programmers can create
// Fn objects that are not Clone
// This will work for all Fn objects, not just closures
// And it is a little bit more efficient for Fn closures as it do not clone itself.
impl<T, R, F> Apply<T, R> for F where F:
Fn(&dyn Apply<T, R>, T) -> R
{
fn apply(&self, f: &dyn Apply<T, R>, t: T) -> R {
self(f, t)
// NOTE: Each letter is an individual symbol.
// (λx.(λy.xxy))(λx.(λy.f(λz.xxz)y))t
// => (λx.xx)(λx.f(xx))t
// => (Yf)t
}
}
// This works for all closures that is Clone, and those are Fn.
// impl<T, R, F> Apply<T, R> for F where F: FnOnce( &Apply<T, R>, T ) -> R + Clone {
// fn apply( &self, f: &Apply<T, R>, t: T ) -> R {
// (self.clone())( f, t )
// // If we were to pass in self as f, we get -
// // NOTE: Each letter is an individual symbol.
// // λf.λt.sft
// // => λs.λt.sst [s/f]
// // => λs.ss
// }
// }
// Before 1.26 we have some limitations and so we need some workarounds. But now impl Trait is stable and we can
// write the following:
fn y<T,R>(f:impl Fn(&dyn Fn(T) -> R, T) -> R) -> impl Fn(T) -> R {
move |t| (
|x: &dyn Apply<T,R>, y| x.apply(x, y)
) (
&|x: &dyn Apply<T,R>, y| f(
&|z| x.apply(x,z),
y
),
t
)
}
// fn y<T,R>(f:impl FnOnce(&Fn(T) -> R, T) -> R + Clone) -> impl FnOnce(T) -> R {
// |t| (|x: &Apply<T,R>,y| x.apply(x,y))
// (&move |x:&Apply<T,R>,y| f(&|z| x.apply(x,z), y), t)
// // NOTE: Each letter is an individual symbol.
// // (λx.(λy.xxy))(λx.(λy.f(λz.xxz)y))t
// // => (λx.xx)(λx.f(xx))t
// // => (Yf)t
// }
// Previous version removed as they are just hacks when impl Trait is not available.
fn fac(n: usize) -> usize {
let almost_fac = |f: &dyn Fn(usize) -> usize, x|
if x == 0 {
1
} else {
x * f(x - 1)
}
;
let fac = y( almost_fac );
fac(n)
}
fn fib( n: usize ) -> usize {
let almost_fib = |f: &dyn Fn(usize) -> usize, x|
if x < 2 {
1
} else {
f(x - 2) + f(x - 1)
};
let fib = y(almost_fib);
fib(n)
}
fn optimal_fib( n: usize ) -> usize {
let almost_fib = |f: &dyn Fn((usize,usize,usize)) -> usize, (i0,i1,x)|
match x {
0 => i0,
1 => i1,
x => f((i1,i0+i1, x-1))
}
;
let fib = |x| y(almost_fib)((1,1,x));
fib(n)
}
fn test_y() {
println!("{}", fac(10));
println!("{}", fib(10));
println!("{}", optimal_fib(10));
}