Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make nannou::app receive a closure. #793

Open
alexfertel opened this issue Sep 18, 2021 · 5 comments · May be fixed by #937
Open

Make nannou::app receive a closure. #793

alexfertel opened this issue Sep 18, 2021 · 5 comments · May be fixed by #937

Comments

@alexfertel
Copy link
Contributor

Currently, nannou::app and, in general, the app::Builder API expect an argument of type fn, but it would be nice to be able to pass a closure instead.

My use case is that I'm writing a CLI wrapper to speed up my workflow, and I need to close over the environment in model, update, and view, but I can't because they have to be of type fn.

I think this wouldn't be a breaking change because you can pass a function in a place where a closure is expected, but not the other way around. However, there may be some nuance I am not aware of.

@akriegman
Copy link

Hi Alex, I had a similar problem to you. What I did is I used the lazy_static package so my command line arguments could be in the global scope. I prefer this design pattern personally.

I think your suggestion is probably the right thing to do. I had a similar problem with the draw.polyline().points() function which takes an IntoIterator over Point2's, which means that I have to move ownership of my list of points every time I draw a path, which in my case meant cloning the list every frame. There seems to be a problem in Nannou of functions that don't take exactly the right types and therefore needlessly exclude certain use cases. I'm not sure whether this is a problem with Nannou or a problem with Rust.

@alexfertel
Copy link
Contributor Author

Ooh, I haven't thought of that, I will try it out, thank you!

@vvcaw
Copy link

vvcaw commented Mar 26, 2022

Hi, has there been any updates on this issue? I think i stumbled over a similar problem, but please correct me if I'm wrong. I'd like to use some data inside the model function inside of an implementation block and came up with this approach.

impl Renderer {
    pub fn preview(&mut self) {
        nannou::app(self.model())
            .update(Renderer::update)
            .simple_window(Renderer::view)
            .run();
    }

    fn model(&self) -> fn(app: &nannou::App) -> Animation {
        move |app: &nannou::App| self.animation
    }
}

But as nannou::app requires a fn and closures can't be converted to function pointers if they capture variables I seem to be stuck. Am I missing something or is there no easy fix for this problem?

@thinkrapido
Copy link

use my fork until then https://github.com/i-think-rapido/nannou/tree/feature/tr/functions-as-closures

@vvzen
Copy link

vvzen commented Mar 11, 2024

+1 - it's really a pity that one has to use global state when closures would have been enough.

This is the relevant type they're using: https://github.com/nannou-org/nannou/blob/c8ac92d6c59b6dbd78bb903207a9c20013f7b7d1/nannou/src/app.rs#L35C1-L35C45

In case this helps anyone, I think there's another (somewhat hacky) way of dealing with this that doesn't without require a lazy static or other global workarounds.

While model has to be a function pointer and not a closure, there's nothing stopping you from doing the cli parsing inside the a closure in your main, e.g.:

impl Model {
    fn new(python_file: PathBuf, some_dir: PathBuf) -> Self {
        Self {
            primitives: Vec::with_capacity(100),
            files_checksums: HashMap::new(),
            python_code: String::new(),
            python_file,
            some_dir,
        }
    }
}


fn main() -> color_eyre::Result<()> {

    // This will block until args are parsed
    // This is an 'hack' to go around a limitation of the nannou API,
    // which require function pointers instead of closures
    Cli::parse();

    let model = move |_app: &App| {
        // Here we actually get the CLI args and we return the model
        let cli = Cli::parse();
        
        let (sketch_file, some_dir) = match cli.command {
            cli::SubCommands::Run {
                sketch_file,
                some_dir,
            } => (sketch_file, some_dir),
        };

        let cwd = std::env::current_dir().expect("Failed to get current working directory");
        let sketch_file = cwd.join(
            sketch_file
                .canonicalize()
                .expect("Failed to canonicalize path"),
        );
        let some_dir = cwd.join(
            some_dir
                .canonicalize()
                .expect("Failed to canonicalize path"),
        );

        Model::new(sketch_file, some_dir)
    };

    // App loop
    nannou::app(model).update(update).simple_window(view).run();

    Ok(())
}

That's because closures that don't actually capture anything from the environment can be coerced to function pointers: https://rust-lang.github.io/rfcs/1558-closure-to-fn-coercion.html

EDIT: That being said, this^ hack only works for people that require parsing CLI args.
And one could write it like this too, without the Fn to fn coercion:

fn main() -> color_eyre::Result<()> {

    // This will block until args are parsed
    // This is an 'hack' to go around a limitation of the nannou API,
    // which require function pointers instead of closures
    crate::cli::Cli::parse();

    // App loop
    nannou::app(model).update(update).simple_window(view).run();

    Ok(())
}

fn model(_app: &App) -> Model {
    // Get the cli args and create the nannou Model
    let cli = Cli::parse();

    let (sketch_file, improvviso_dir) = match cli.command {
        cli::SubCommands::Run {
            sketch_file,
            some_dir,
        } => (sketch_file, some_dir),
    };
    // same stuff..

    Model::new(sketch_file, some_dir)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants