Made an actual working example!
This commit is contained in:
parent
83053ca8bb
commit
7a6d29da81
@ -10,8 +10,6 @@ use crate::scratch;
|
||||
|
||||
fn cube_thing() -> Rule {
|
||||
|
||||
let mesh = prim::cube();
|
||||
|
||||
// Quarter-turn in radians:
|
||||
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(),
|
||||
];
|
||||
|
||||
let rec = || -> RuleEval {
|
||||
let rec = move |self_: Rc<Rule>| -> RuleEval {
|
||||
let gen_rulestep = |rot: &Mat4| -> Child {
|
||||
let m: Mat4 = rot *
|
||||
Matrix4::new_scaling(0.5) *
|
||||
geometry::Translation3::new(6.0, 0.0, 0.0).to_homogeneous();
|
||||
Child {
|
||||
rule: Rule { eval: Box::new(rec) },
|
||||
rule: self_.clone(),
|
||||
xf: m,
|
||||
vmap: vec![],
|
||||
}
|
||||
};
|
||||
|
||||
RuleEval {
|
||||
geom: mesh.clone(),
|
||||
geom: prim::cube(),
|
||||
final_geom: prim::empty_mesh(),
|
||||
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) }
|
||||
}
|
||||
|
||||
@ -492,7 +508,16 @@ pub fn main() {
|
||||
println!("Writing {}...", fname);
|
||||
mesh.write_stl_file(&fname).unwrap();
|
||||
}
|
||||
*/
|
||||
*/
|
||||
|
||||
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");
|
||||
@ -516,6 +541,11 @@ pub fn main() {
|
||||
// let f = 20;
|
||||
// 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];
|
||||
|
||||
|
||||
17
src/rule.rs
17
src/rule.rs
@ -1,5 +1,6 @@
|
||||
use crate::openmesh::{OpenMesh, Tag, Mat4};
|
||||
//use crate::prim;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 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
|
||||
/// geometry
|
||||
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
|
||||
// no function call.
|
||||
@ -51,7 +52,7 @@ pub struct RuleEval {
|
||||
pub struct Child {
|
||||
|
||||
/// Rule to evaluate to produce geometry
|
||||
pub rule: Rule,
|
||||
pub rule: Rc<Rule>,
|
||||
|
||||
/// The transform to apply to all geometry produced by `rule`
|
||||
/// (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).
|
||||
/// `iters_left` sets the maximum recursion depth. This returns
|
||||
/// (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 rs: RuleEval = (self.eval)();
|
||||
let rs: RuleEval = (s.eval)(s.clone());
|
||||
if iters_left <= 0 {
|
||||
return (rs.final_geom, 1);
|
||||
// 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| {
|
||||
// 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:
|
||||
evals += eval;
|
||||
|
||||
@ -112,7 +113,7 @@ impl Rule {
|
||||
/// This should be identical to to_mesh, but implemented
|
||||
/// iteratively with an explicit stack rather than with recursive
|
||||
/// 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 {
|
||||
// The set of rules we're currently handling:
|
||||
@ -133,7 +134,7 @@ impl Rule {
|
||||
// (usually because they involve multiple rules).
|
||||
//
|
||||
// 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 {
|
||||
rules: eval.children,
|
||||
next: 0,
|
||||
@ -162,7 +163,7 @@ impl Rule {
|
||||
|
||||
// Evaluate the rule:
|
||||
let child = &s.rules[s.next];
|
||||
let mut eval = (child.rule.eval)();
|
||||
let mut eval = (child.rule.eval)(child.rule.clone());
|
||||
eval_count += 1;
|
||||
|
||||
// Make an updated world transform:
|
||||
|
||||
177
src/scratch.rs
177
src/scratch.rs
@ -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));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user